From 54d8d3fbfdbad6b8986fa1d5a0a67ca1ef7f3c93 Mon Sep 17 00:00:00 2001 From: Dmitriy Kovalenko Date: Wed, 13 Dec 2023 16:22:05 +0100 Subject: [PATCH] feat: Rework all the sensors --- cli/.editorconfig => .editorconfig | 0 .gitignore | 4 +- cli/Cargo.lock | 63 +++++++---- cli/Cargo.toml | 4 + cli/link.sh | 6 ++ cli/src/bluetooth.rs | 85 ++++++--------- cli/src/climate_data.rs | 20 ++++ cli/src/main.rs | 37 ++++++- cli/src/reactions/data_reaction.rs | 110 +++++++++++++++++++ cli/src/reactions/mod.rs | 24 +++++ cli/src/reactions/window.rs | 87 +++++++++++++++ cli/src/tui_app.rs | 100 ++++++++++-------- co2.code-workspace | 25 ----- core/.vscode/extensions.json | 10 -- core/.vscode/settings.json | 9 -- core/extra_script.py | 9 ++ core/platformio.ini | 8 +- core/src/i2c_scanner.cpp | 2 +- core/src/main.cpp | 105 +++++++++++------- core/src/tempreature_humiidty.rs | 65 ------------ windows/.gitignore | 5 + windows/platformio.ini | 5 +- windows/src/battery.h | 96 +++++++++++++++++ windows/src/main.cpp | 164 ++++++++++++++++++++++------- 24 files changed, 737 insertions(+), 306 deletions(-) rename cli/.editorconfig => .editorconfig (100%) create mode 100644 cli/link.sh create mode 100644 cli/src/climate_data.rs create mode 100644 cli/src/reactions/data_reaction.rs create mode 100644 cli/src/reactions/mod.rs create mode 100644 cli/src/reactions/window.rs delete mode 100644 co2.code-workspace delete mode 100644 core/.vscode/extensions.json delete mode 100644 core/.vscode/settings.json create mode 100644 core/extra_script.py delete mode 100644 core/src/tempreature_humiidty.rs create mode 100644 windows/.gitignore create mode 100644 windows/src/battery.h diff --git a/cli/.editorconfig b/.editorconfig similarity index 100% rename from cli/.editorconfig rename to .editorconfig diff --git a/.gitignore b/.gitignore index 876067b..2de8bd0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ target/ .pio/ .DS_Store -*.log \ No newline at end of file +*.log +compile_commands.json +.cache/ diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 704f447..fc94172 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -131,7 +131,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", ] [[package]] @@ -148,7 +148,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", ] [[package]] @@ -357,12 +357,16 @@ dependencies = [ name = "cli" version = "0.1.0" dependencies = [ + "async-trait", "btleplug", "chrono", "crossterm 0.26.1", "futures", + "lazy_static", "notify-rust", "pretty_env_logger", + "serde", + "serde_json", "spinners", "textplots", "throbber-widgets-tui", @@ -668,7 +672,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", ] [[package]] @@ -812,7 +816,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", ] [[package]] @@ -1386,9 +1390,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -1410,9 +1414,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.27" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1522,6 +1526,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + [[package]] name = "same-file" version = "1.0.6" @@ -1539,9 +1549,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -1569,13 +1579,24 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", ] [[package]] @@ -1586,7 +1607,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", ] [[package]] @@ -1767,9 +1788,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -1847,7 +1868,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", ] [[package]] @@ -1935,7 +1956,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", ] [[package]] @@ -2011,7 +2032,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", ] [[package]] @@ -2172,7 +2193,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", "wasm-bindgen-shared", ] @@ -2194,7 +2215,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 64e2fbc..ba4bbbe 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -6,12 +6,16 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-trait = "0.1.68" btleplug = { version = "0.10", features = ["serde"] } chrono = "0.4.24" crossterm = "0.26.1" futures = "0.3.28" +lazy_static = "1.4.0" notify-rust = "4.8.0" pretty_env_logger = "0.4.0" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" spinners = "4.1.0" textplots = "0.8.0" throbber-widgets-tui = "0.1.3" diff --git a/cli/link.sh b/cli/link.sh new file mode 100644 index 0000000..7ad9905 --- /dev/null +++ b/cli/link.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -euf -o pipefail + +cargo build --release +cp target/release/cli /usr/local/bin/co2 diff --git a/cli/src/bluetooth.rs b/cli/src/bluetooth.rs index 8d47807..7bb18e0 100644 --- a/cli/src/bluetooth.rs +++ b/cli/src/bluetooth.rs @@ -1,9 +1,8 @@ -use btleplug::api::{Central, Manager as _, Peripheral, ScanFilter}; +use btleplug::api::{Central, CharPropFlags, Manager as _, Peripheral, ScanFilter}; use btleplug::platform::Manager; use futures::stream::StreamExt; use std::error::Error; -use std::str::FromStr; use std::time::Duration; use tokio::time::{self, sleep, timeout}; use uuid::Uuid; @@ -11,45 +10,18 @@ use uuid::Uuid; /// Only devices whose name contains this string will be tried. const PERIPHERAL_NAME_MATCH_FILTER: &str = "CO2CICKA"; -#[derive(Debug)] -pub struct ClimateData { - pub temperature: f32, - pub e_co2: i32, - pub tvoc: i32, - pub pressure: f32, - pub humidity: f32, - pub light: f32, -} - -impl ClimateData { - fn from_bytes(data: Vec) -> Self { - let str = String::from_utf8(data).unwrap(); - - let encoded_data = str.split(',').collect::>(); - let co2 = encoded_data[0].parse::().unwrap_or_default(); - let tvoc = encoded_data[1].parse::().unwrap_or_default(); - let temp = encoded_data[2].parse::().unwrap_or_default(); - let pressure = encoded_data[3].parse::().unwrap_or_default(); - let humidity = encoded_data[4].parse::().unwrap_or_default(); - let light = encoded_data[5].parse::().unwrap_or_default(); - - Self { - temperature: temp, - e_co2: co2, - tvoc, - pressure, - humidity, - light, - } - } -} - pub struct Connection { - manager: Manager, + _manager: Manager, peripheral: TPeripheral, characteristic: btleplug::api::Characteristic, } +pub trait FromBleData { + fn from_bytes(data: Vec) -> Result> + where + Self: Sized; +} + const TIMEOUT: Duration = Duration::from_secs(10); impl Connection { @@ -81,20 +53,21 @@ impl Connection { } } - pub async fn subscribe_to_sensor( + pub async fn subscribe_to_sensor( &self, mut fun: TFun, ) -> Result<(), Box> { tracing::debug!("Subscribing to sensor"); self.peripheral.subscribe(&self.characteristic).await?; - let mut notification_stream = self.peripheral.notifications().await?; while let Some(data) = timeout(TIMEOUT, notification_stream.next()).await? { - let data = ClimateData::from_bytes(data.value); tracing::debug!("Received data from sensor {data:?}"); + match TData::from_bytes(data.value) { + Ok(data) => fun(data), + Err(e) => tracing::error!("Error decodring data from sensor {}", e), + } - fun(data); sleep(Duration::from_millis(5000)).await; tracing::debug!("Awake"); @@ -109,11 +82,20 @@ impl Connection { Ok(()) } -} -pub async fn find_sensor() -> Result, Box> { - let notify_characteristic_uuid = Uuid::from_str("0000FFE1-0000-1000-8000-00805F9B34FB")?; + pub async fn read_from_sensor(&self) -> Result> { + tracing::debug!("Reading sensor"); + + Ok(TData::from_bytes( + self.peripheral.read(&self.characteristic).await?, + )?) + } +} +pub async fn find_sensor( + characteristic_uuid: Uuid, + property: CharPropFlags, +) -> Result, Box> { let manager = Manager::new().await?; let adapter_list = manager.adapters().await?; if adapter_list.is_empty() { @@ -121,10 +103,7 @@ pub async fn find_sensor() -> Result, Box } for adapter in adapter_list.iter() { - adapter - .start_scan(ScanFilter::default()) - .await - .expect("Can't scan BLE adapter for connected devices..."); + adapter.start_scan(ScanFilter::default()).await?; time::sleep(Duration::from_secs(2)).await; let peripherals = adapter.peripherals().await?; @@ -160,10 +139,14 @@ pub async fn find_sensor() -> Result, Box if is_connected { peripheral.discover_services().await?; + for characteristic in peripheral.characteristics().into_iter() { - if characteristic.uuid == notify_characteristic_uuid { + if characteristic.uuid == characteristic_uuid + && characteristic.properties.contains(property) + { + tracing::debug!("Found characteristic {:?}", characteristic.uuid,); return Ok(Connection { - manager, + _manager: manager, peripheral, characteristic, }); @@ -179,7 +162,9 @@ pub async fn find_sensor() -> Result, Box } } } + + adapter.stop_scan().await?; } - Err("No O found".into()) + Err("No devices found".into()) } diff --git a/cli/src/climate_data.rs b/cli/src/climate_data.rs new file mode 100644 index 0000000..3b1f5c1 --- /dev/null +++ b/cli/src/climate_data.rs @@ -0,0 +1,20 @@ +use crate::bluetooth::FromBleData; +use serde::Deserialize; +use std::error::Error; + +#[derive(Debug, Clone, Copy, Deserialize)] +pub struct ClimateData { + pub co2: i32, + pub temperature: f32, + pub eco2: i16, + pub etvoc: i16, + pub pressure: f32, + pub humidity: f32, + pub light: Option, +} + +impl FromBleData for ClimateData { + fn from_bytes(data: Vec) -> Result> { + Ok(serde_json::from_slice(&data)?) + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 0981475..0c2d462 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,10 +1,29 @@ mod tui_app; +use btleplug::api::CharPropFlags; +use climate_data::ClimateData; +use spinners::{Spinner, Spinners}; use tui_app::TerminalUi; +use uuid::Uuid; mod bluetooth; -use crate::bluetooth::find_sensor; use std::error::Error; use tui::{backend::CrosstermBackend, Terminal}; +use crate::reactions::run_reactions; +mod climate_data; +mod reactions; + +const CORE_CHARACTERISTIC_UUID: Uuid = Uuid::from_u128(0x0000FFE1_0000_1000_8000_00805F9B34FB); + +fn set_terminal_title(climate_data: &ClimateData) { + use std::io::Write; + + print!( + "\x1B]0;T {}°C; CO2 {} ppm; H {:.1}%\x07", + climate_data.temperature, climate_data.co2, climate_data.humidity + ); + std::io::stdout().flush().unwrap(); +} + #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); @@ -13,23 +32,31 @@ async fn main() -> Result<(), Box> { let backend = CrosstermBackend::new(stdout); let mut app = TerminalUi::new()?; let mut terminal = Terminal::new(backend)?; + let mut history = Vec::new(); loop { - // let mut spinner = Spinner::new(Spinners::Dots9, "Connecting to sensor".to_owned()); + let mut spinner = Spinner::new(Spinners::Dots9, "Connecting to sensor".to_owned()); tracing::debug!("Looking for a sensor..."); - if let Ok(connection) = find_sensor().await { + if let Ok(connection) = + bluetooth::find_sensor(CORE_CHARACTERISTIC_UUID, CharPropFlags::NOTIFY).await + { let mut spinner_stopped = false; match connection .subscribe_to_sensor(|data| { + tracing::debug!("New climate data: {:?}", data); if !spinner_stopped { - // spinner.stop(); + spinner.stop(); terminal.clear().unwrap(); spinner_stopped = true } - app.capture_measurements(data); + set_terminal_title(&data); + app.capture_measurements(&data); app.draw(&mut terminal); + history.push(data); + + run_reactions(&history); }) .await { diff --git a/cli/src/reactions/data_reaction.rs b/cli/src/reactions/data_reaction.rs new file mode 100644 index 0000000..36e3577 --- /dev/null +++ b/cli/src/reactions/data_reaction.rs @@ -0,0 +1,110 @@ +use crate::climate_data::ClimateData; +use async_trait::async_trait; +use std::cmp::Ordering; +use std::error::Error; +use std::time::Duration; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Trend { + Up, + Down, + None, +} + +#[async_trait] +pub trait DataReaction { + const PERIOD: Duration; + const TREND: Trend; + + fn get_value(data: &ClimateData) -> T; + fn only_if(latest_data: &ClimateData) -> bool; + + #[allow(unused)] + fn force_run(latest_data: &ClimateData) -> bool { + false + } + + async fn run() -> Result<(), Box>; + + fn validate(values: &[ClimateData]) -> bool { + let self_type_name = std::any::type_name::(); + + let last_data = if let Some(last_data) = values.last() { + last_data + } else { + tracing::debug!(?self_type_name, "reaction not needed – no data"); + return false; + }; + + if Self::force_run(last_data) { + tracing::debug!(?self_type_name, "reaction forced"); + return true; + } + + let only_if = Self::only_if(last_data); + if !only_if { + tracing::debug!( + ?self_type_name, + "reaction not needed – only_if returned false" + ); + return false; + } + + // we only run reaction once per specified period of time + let period_size = Self::PERIOD.as_secs() / 5; + if values.len() < period_size as usize { + tracing::debug!(?self_type_name, "reaction not needed – not enough data"); + return false; + } + + if values.len() % period_size as usize != 0 { + tracing::debug!( + ?self_type_name, + "reaction not needed – waiting for next period" + ); + return false; + } + + let trend_check_values = &values[values.len() - period_size as usize..]; + let current_trend = Self::get_trend(trend_check_values); + let trend_sync = current_trend == Self::TREND; + + if !trend_sync { + tracing::debug!( + ?self_type_name, + "reaction not needed – trend is not matching. Expected {:?}, got {current_trend:?}", + Self::TREND + ); + return false; + } + + trend_sync + } + + fn get_trend(values: &[ClimateData]) -> Trend { + let len = values.len(); + if len < 2 { + return Trend::None; + } + + let mut up_count = 0; + let mut down_count = 0; + + for i in 1..len { + let a = Self::get_value(&values[i - 1]); + let b = Self::get_value(&values[i]); + + if a > b { + up_count += 1; + } else if a < b { + down_count += 1; + } + } + + match up_count.cmp(&down_count) { + Ordering::Greater => Trend::Up, + Ordering::Less => Trend::Down, + Ordering::Equal => Trend::None, + } + } +} diff --git a/cli/src/reactions/mod.rs b/cli/src/reactions/mod.rs new file mode 100644 index 0000000..470681d --- /dev/null +++ b/cli/src/reactions/mod.rs @@ -0,0 +1,24 @@ +mod window; +pub use window::*; + +use crate::reactions::data_reaction::DataReaction; +mod data_reaction; + +macro_rules! define_reactions { + ($($x: ty), +) => { + pub fn run_reactions(data: &[crate::climate_data::ClimateData]) -> () { + $({ + if <$x>::validate(data) { + tokio::spawn(async move { + if let Err(e) = <$x>::run().await { + tracing::error!("Error running reaction {}: {}", std::any::type_name::<$x>(), e) + } + }); + } + })+ + } + + }; +} + +define_reactions!(WindowData); diff --git a/cli/src/reactions/window.rs b/cli/src/reactions/window.rs new file mode 100644 index 0000000..e237176 --- /dev/null +++ b/cli/src/reactions/window.rs @@ -0,0 +1,87 @@ +use crate::{ + bluetooth::{self, FromBleData}, + climate_data::ClimateData, + reactions::{data_reaction::Trend, DataReaction}, +}; +use btleplug::api::{CharPropFlags, Peripheral}; +use chrono::Timelike; +use std::{error::Error, time::Duration}; +use tokio::process::Command; +use uuid::Uuid; + +const WINDOWS_CHARACTERISTIC_UUID: Uuid = Uuid::from_u128(0x19B10000_E8F2_537E_4F6C_D104768A1214); + +#[derive(Debug)] +pub struct WindowData { + pub is_closed: bool, +} + +impl FromBleData for WindowData { + fn from_bytes(data: Vec) -> Result> { + Ok(Self { + is_closed: data[0] == 1, + }) + } +} + +impl WindowData { + async fn find_connection() -> Result, Box> { + for _ in 0..30 { + match bluetooth::find_sensor( + WINDOWS_CHARACTERISTIC_UUID, + CharPropFlags::NOTIFY | CharPropFlags::READ, + ) + .await + { + Ok(connection) => return Ok(connection), + Err(_) => continue, + } + } + + Err("Could not find window sensor".into()) + } + + pub async fn get_data() -> Result> { + let connection = Self::find_connection().await?; + let data = connection.read_from_sensor::().await?; + connection.disconnect().await?; + + Ok(data) + } +} + +#[async_trait::async_trait] +impl DataReaction for WindowData { + const PERIOD: Duration = Duration::from_secs(60); + const TREND: Trend = Trend::Up; + + fn get_value(data: &ClimateData) -> f32 { + data.light.unwrap_or(0.) + } + + fn force_run(latest_data: &ClimateData) -> bool { + latest_data.light.unwrap_or(0.) > 1200. + } + + fn only_if(latest_data: &ClimateData) -> bool { + tracing::debug!("Check if required to check window data"); + let hour = chrono::Local::now().hour(); + + hour > 8 && hour < 20 && latest_data.light.unwrap_or(0.0) > 800. + } + + async fn run() -> Result<(), Box> { + tracing::info!("Run window reaction"); + let data = WindowData::get_data().await?; + tracing::info!("Window data: {:?}", data); + + if data.is_closed { + Command::new("shortcuts") + .args(["run", "Закрой шторы"]) + .output() + .await?; + } + + Ok(()) + } +} diff --git a/cli/src/tui_app.rs b/cli/src/tui_app.rs index d184b20..d653866 100644 --- a/cli/src/tui_app.rs +++ b/cli/src/tui_app.rs @@ -1,4 +1,4 @@ -use crossterm::event::{self, Event, KeyCode}; +use crate::climate_data::ClimateData; use std::error::Error; use tui::{ backend::Backend, @@ -9,16 +9,14 @@ use tui::{ widgets::{Axis, Block, Borders, Chart, Dataset, Paragraph, Wrap}, Frame, Terminal, }; - -use crate::bluetooth::ClimateData; - pub enum UiState { Spinner(String), Connected, } pub struct TerminalUi { - data: Vec<(f64, f64)>, + co2_history: Vec<(f64, f64)>, + eco2_history: Vec<(f64, f64)>, last_climate_data: Option, window: [f64; 2], state: UiState, @@ -29,20 +27,24 @@ impl TerminalUi { let now = chrono::offset::Local::now().timestamp_millis() as f64; Ok(Self { - data: vec![], + eco2_history: vec![], + co2_history: vec![], window: [now, now], last_climate_data: None, state: UiState::Spinner("Connecting to sensor...".to_string()), }) } - pub fn capture_measurements(&mut self, climate_data: ClimateData) { + pub fn capture_measurements(&mut self, climate_data: &ClimateData) { let now = chrono::offset::Local::now().timestamp_millis(); self.state = UiState::Connected; - self.data.push((now as f64, climate_data.e_co2 as f64)); + self.eco2_history + .push((now as f64, climate_data.eco2 as f64)); + self.co2_history.push((now as f64, climate_data.co2 as f64)); + self.window[1] = now as f64; - self.last_climate_data = Some(climate_data); + self.last_climate_data = Some(*climate_data); } fn render_dashboard(&self, f: &mut Frame) { @@ -75,17 +77,25 @@ impl TerminalUi { Style::default().add_modifier(Modifier::BOLD), ), ]; - let datasets = vec![Dataset::default() - .name("ppm") - .marker(symbols::Marker::Dot) - .style(Style::default().fg(Color::Cyan)) - .data(&app.data)]; + + let datasets = vec![ + Dataset::default() + .name("eCO2 ppm") + .marker(symbols::Marker::Dot) + .style(Style::default().fg(Color::Magenta)) + .data(&app.eco2_history), + Dataset::default() + .name("CO2 ppm") + .marker(symbols::Marker::Dot) + .style(Style::default().fg(Color::Cyan)) + .data(&app.co2_history), + ]; let chart = Chart::new(datasets) .block( Block::default() .title(Span::styled( - " eCO2 ppm ", + " CO2 ppm ", Style::default() .fg(Color::Cyan) .add_modifier(Modifier::BOLD), @@ -105,9 +115,9 @@ impl TerminalUi { .style(Style::default().fg(Color::Gray)) .labels(vec![ Span::styled("400", Style::default().add_modifier(Modifier::BOLD)), - Span::styled("1400.0", Style::default().add_modifier(Modifier::BOLD)), + Span::styled("2000.0", Style::default().add_modifier(Modifier::BOLD)), ]) - .bounds([400.0, 1400.0]), + .bounds([400.0, 2000.0]), ); f.render_widget(chart, chunks[1]); } @@ -123,7 +133,7 @@ impl TerminalUi { ]; let block = Paragraph::new(text) - .block(Block::default().title("Paragraph").borders(Borders::ALL)) + .block(Block::default().title(title).borders(Borders::ALL)) .style(Style::default().fg(Color::White).bg(Color::Black)) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); @@ -152,18 +162,6 @@ impl TerminalUi { .unwrap(); } - pub fn poll_interactions(&self, timeout: core::time::Duration) { - loop { - if crossterm::event::poll(timeout).unwrap() { - if let Event::Key(key) = event::read().unwrap() { - if let KeyCode::Char('q') = key.code { - std::process::exit(0); - } - } - } - } - } - fn render_overview( &self, last_climate_data: &ClimateData, @@ -171,6 +169,31 @@ impl TerminalUi { chunks: &[layout::Rect], ) { let text = vec![ + Spans::from(vec![ + Span::from("CO2: "), + Span::styled( + format!("{} ppm ", last_climate_data.co2), + Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + ), + Span::from(match last_climate_data.co2 { + co2 if co2 > 1000 => "🥵", + co2 if co2 > 800 => "😨", + co2 if co2 > 600 => "😗", + co2 if co2 > 400 => "😊", + _ => "😌", + }), + ]), + Spans::from(vec![ + Span::from("eCO2: "), + Span::styled( + format!("{} ppm", last_climate_data.eco2), + Style::default() + .fg(Color::Gray) + .add_modifier(Modifier::BOLD), + ), + ]), Spans::from(vec![ Span::from("Temperature: "), Span::styled( @@ -199,32 +222,23 @@ impl TerminalUi { Spans::from(vec![ Span::from("TVOC: "), Span::styled( - format!("{} ppb", last_climate_data.tvoc), + format!("{} ppb", last_climate_data.etvoc), Style::default() .fg(Color::Gray) .add_modifier(Modifier::BOLD), ), ]), - Spans::from(vec![ - Span::from("eCO2: "), - Span::styled( - format!("{} ppm", last_climate_data.e_co2), - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ), - ]), Spans::from(vec![ Span::from("Light: "), Span::styled( - format!("{} lux ", last_climate_data.light), + format!("{} lux ", last_climate_data.light.unwrap_or(0.0)), Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ), Span::from(match last_climate_data.light { - light if light > 400.0 => "🌞", - light if light > 100.0 => "️⛅", + Some(light) if light > 400.0 => "🌞", + Some(light) if light > 100.0 => "️⛅", _ => "🌚", }), ]), diff --git a/co2.code-workspace b/co2.code-workspace deleted file mode 100644 index 3513462..0000000 --- a/co2.code-workspace +++ /dev/null @@ -1,25 +0,0 @@ -{ - "folders": [ - { - "path": "cli" - }, - { - "path": "core" - }, - { - "path": "windows" - } - ], - "settings": { - "files.associations": { - "*.prisma": "graphql", - "array": "cpp", - "limits": "cpp", - "type_traits": "cpp", - "*.inc": "cpp" - }, - "cSpell.words": [ - "tvoc" - ] - } -} \ No newline at end of file diff --git a/core/.vscode/extensions.json b/core/.vscode/extensions.json deleted file mode 100644 index 080e70d..0000000 --- a/core/.vscode/extensions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} diff --git a/core/.vscode/settings.json b/core/.vscode/settings.json deleted file mode 100644 index 82aa626..0000000 --- a/core/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "files.associations": { - "*.prisma": "graphql", - "array": "cpp", - "limits": "cpp", - "type_traits": "cpp", - "*.inc": "cpp" - } -} \ No newline at end of file diff --git a/core/extra_script.py b/core/extra_script.py new file mode 100644 index 0000000..9225c8d --- /dev/null +++ b/core/extra_script.py @@ -0,0 +1,9 @@ +import os +Import("env") + +# include toolchain paths +env.Replace(COMPILATIONDB_INCLUDE_TOOLCHAIN=False) + +# override compilation DB path +env.Replace(COMPILATIONDB_PATH=os.path.join("compile_commands.json")) +print("BUILD_DIR: ", env.get("COMPILATIONDB_PATH")) diff --git a/core/platformio.ini b/core/platformio.ini index 8da539e..64ae5dd 100644 --- a/core/platformio.ini +++ b/core/platformio.ini @@ -10,9 +10,11 @@ [env:pico32] platform = espressif32 -board = pico32 +board = nodemcu-32s framework = arduino -monitor_speed = 115200 +monitor_speed = 9600 +extra_scripts = pre:extra_script.py +upload_port = /dev/tty.SLAB_USBtoUART lib_deps = adafruit/Adafruit SGP30 Sensor@^2.0.0 adafruit/DHT sensor library@^1.4.4 @@ -20,3 +22,5 @@ lib_deps = adafruit/Adafruit Si7021 Library@^1.5.1 adafruit/Adafruit BMP280 Library@^2.6.6 claws/BH1750@^1.3.0 + wifwaf/MH-Z19@^1.5.4 + mobizt/FirebaseJson@^3.0.8 diff --git a/core/src/i2c_scanner.cpp b/core/src/i2c_scanner.cpp index 1f3ed9c..f47abc6 100644 --- a/core/src/i2c_scanner.cpp +++ b/core/src/i2c_scanner.cpp @@ -30,4 +30,4 @@ void scan_i2c_devices(TwoWire *wire) { Serial.println("No devices found."); } -} \ No newline at end of file +} diff --git a/core/src/main.cpp b/core/src/main.cpp index 01d5885..5575b27 100644 --- a/core/src/main.cpp +++ b/core/src/main.cpp @@ -1,58 +1,62 @@ -#include -#include -#include "ccs811.h" // include library for CCS811 - Sensor from martin-pennings https://github.com/maarten-pennings/CCS811 -#include "Adafruit_Si7021.h" // include main library for SI7021 - Sensor #include "Adafruit_BMP280.h" // include main library for BMP280 - Sensor +#include "Adafruit_Si7021.h" // include main library for SI7021 - Sensor +#include "MHZ19.h" +#include "ccs811.h" +#include "i2c_scanner.h" +#include +#include +#include +#include #include #include #include -#include -#include -#include "i2c_scanner.h" +#include +#include + +#define BAUDRATE 9600 // DHT dht(DHTPIN, DHTTYPE); CCS811 ccs811; Adafruit_BMP280 bmp280; // I2C Adafruit_Si7021 SI702x = Adafruit_Si7021(); BH1750 lightSensor(0x23); +MHZ19 myMHZ19; + +HardwareSerial mySerial(2); BLECharacteristic *pCharacteristic; bool deviceConnected = false; -class MyServerCallbacks : public BLEServerCallbacks -{ - void onConnect(BLEServer *pServer) - { +class MyServerCallbacks : public BLEServerCallbacks { + void onConnect(BLEServer *pServer) { deviceConnected = true; Serial.println("***** Connect"); } - void onDisconnect(BLEServer *pServer) - { + void onDisconnect(BLEServer *pServer) { Serial.println("***** Disconnect"); deviceConnected = false; pServer->getAdvertising()->start(); } }; -void setup() -{ - Serial.begin(115200); +void setup() { + Serial.begin(9600); Serial.println("DHT22 Temperature and Humidity Sensor"); Serial.println("------------------------------------"); Wire.begin(21, 22); // Configure I2C pins (SDA: GPIO 21, SCL: GPIO 22) Wire1.begin(26, 25); + mySerial.begin(BAUDRATE); // sensor serial - if (!lightSensor.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23, &Wire1)) - { + if (!lightSensor.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23, &Wire1)) { Serial.println("Error initializing BH1750"); } - ccs811.set_i2cdelay(50); // Needed for ESP8266 because it doesn't handle I2C clock stretch correctly - if (!ccs811.begin()) - { + ccs811.set_i2cdelay(50); // Needed for ESP8266 because it doesn't handle I2C + // clock stretch correctly + if (!ccs811.begin()) { Serial.println("Failed to start CSS811! Please check your wiring."); } @@ -69,20 +73,17 @@ void setup() Serial.println("setup: CCS811 start FAILED"); Serial.println("BMP280 test"); /* --- SETUP BMP on 0x76 ------ */ - if (!bmp280.begin(0x76)) - { + if (!bmp280.begin(0x76)) { Serial.println("Could not find a valid BMP280 sensor, check wiring!"); } Serial.println("Si7021 test!"); /* ---- SETUP SI702x ----- */ - if (!SI702x.begin()) - { + if (!SI702x.begin()) { Serial.println("Did not find Si702x sensor!"); } Serial.print("Found model "); - switch (SI702x.getModel()) - { + switch (SI702x.getModel()) { case SI_Engineering_Samples: Serial.print("SI engineering samples"); break; @@ -106,6 +107,9 @@ void setup() Serial.print(SI702x.sernum_a, HEX); Serial.println(SI702x.sernum_b, HEX); + myMHZ19.begin(mySerial); + myMHZ19.autoCalibration(true); + // Wait for the sensors to stabilize delay(2000); @@ -114,13 +118,13 @@ void setup() BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); - BLEService *pService = pServer->createService(BLEUUID("0000FFE0-0000-1000-8000-00805F9B34FB")); + BLEService *pService = + pServer->createService(BLEUUID("0000FFE0-0000-1000-8000-00805F9B34FB")); // Create a BLE Characteristic pCharacteristic = pService->createCharacteristic( BLEUUID("0000FFE1-0000-1000-8000-00805F9B34FB"), - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_NOTIFY); + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); pCharacteristic->addDescriptor(new BLE2902()); pService->start(); @@ -131,8 +135,15 @@ void setup() pAdvertising->start(); } -void loop() -{ +void loop() { + int CO2 = myMHZ19.getCO2(); + int8_t Temp = myMHZ19.getTemperature(); // Request Temperature (as Celsius) + + Serial.print("CO2 (ppm): "); + Serial.println(CO2); + Serial.print("MHZ19 => Temperature (C): "); + Serial.println(Temp); + float lightIntensity = lightSensor.readLightLevel(); Serial.print("Light Intensity: "); Serial.print(lightIntensity); @@ -160,17 +171,37 @@ void loop() ccs811.set_envdata(temperature, humidity); ccs811.read(&eco2, &etvoc, &errstat, &raw); - if (errstat == CCS811_ERRSTAT_OK) - { + if (errstat == CCS811_ERRSTAT_OK) { Serial.print("CCS811 => CO2 = "); Serial.print(eco2); Serial.print("ppm, TVOC = "); Serial.println(etvoc); } - String sensorData = String(eco2) + "," + String(etvoc) + "," + String(temperature) + "," + String(pressure) + "," + String(humidity) + "," + String(lightIntensity); - pCharacteristic->setValue(sensorData.c_str()); + FirebaseJson json; + + if (CO2 && Temp) { + json.add("co2", CO2); + json.add("tempereture_of_co2_sensor", Temp); + } + + if (lightIntensity > 0) { + json.add("light", lightIntensity); + } + + if (temperature && pressure && humidity) { + json.add("temperature", temperature); + json.add("pressure", pressure); + json.add("humidity", humidity); + } + + json.add("eco2", eco2); + json.add("etvoc", etvoc); + + json.toString(Serial, true); + + pCharacteristic->setValue(json.raw()); pCharacteristic->notify(); delay(5000); -} \ No newline at end of file +} diff --git a/core/src/tempreature_humiidty.rs b/core/src/tempreature_humiidty.rs deleted file mode 100644 index 0848343..0000000 --- a/core/src/tempreature_humiidty.rs +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include -#include -#include -#include - -#define DHTPIN 21 // GPIO pin connected to the DHT22 data pin -#define DHTTYPE DHT22 // DHT sensor type (DHT11 or DHT22) - -DHT_Unified dht(DHTPIN, DHTTYPE); - -void setup() -{ - Serial.begin(115200); - dht.begin(); - sensor_t sensor; - dht.temperature().getSensor(&sensor); - Serial.println("DHT22 Temperature and Humidity Sensor"); - Serial.println("------------------------------------"); - Serial.print("Sensor: "); - Serial.println(sensor.name); - Serial.print("Driver Ver: "); - Serial.println(sensor.version); - Serial.print("Unique ID: "); - Serial.println(sensor.sensor_id); - Serial.print("Max Value: "); - Serial.print(sensor.max_value); - Serial.println("°C"); - Serial.print("Min Value: "); - Serial.print(sensor.min_value); - Serial.println("°C"); - Serial.print("Resolution: "); - Serial.print(sensor.resolution); - Serial.println("°C"); - Serial.println("------------------------------------"); - delay(2000); // Allow the sensor to stabilize -} - -void loop() -{ - sensors_event_t event; - dht.temperature().getEvent(&event); - if (isnan(event.temperature)) - { - Serial.println("Error reading temperature!"); - } - else - { - Serial.print("Temperature: "); - Serial.print(event.temperature); - Serial.println("°C"); - } - dht.humidity().getEvent(&event); - if (isnan(event.relative_humidity)) - { - Serial.println("Error reading humidity!"); - } - else - { - Serial.print("Humidity: "); - Serial.print(event.relative_humidity); - Serial.println("%"); - } - delay(2000); // Delay between readings -} \ No newline at end of file diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/windows/platformio.ini b/windows/platformio.ini index 58e995d..09f6127 100644 --- a/windows/platformio.ini +++ b/windows/platformio.ini @@ -12,6 +12,9 @@ platform = nordicnrf52 board = xiao_ble_sense framework = arduino -lib_deps = +monitor_speed = 115200 +monitor_dtr = 0 +lib_deps = adafruit/Adafruit_VL53L0X@^1.2.2 adafruit/Adafruit TinyUSB Library@^2.2.1 + adafruit/Adafruit SleepyDog Library@^1.6.4 diff --git a/windows/src/battery.h b/windows/src/battery.h new file mode 100644 index 0000000..d06b1a5 --- /dev/null +++ b/windows/src/battery.h @@ -0,0 +1,96 @@ +#include +#include + +const double vRef = 2.4; +const unsigned int numReadings = + 1024; // 10-bit ADC readings 0-1023, so the factor is 1024 + +const float MIN_VOLTAGE = 3.6; // Minimum battery voltage +const float MAX_VOLTAGE = 4.1; // Maximum battery voltage +const int MIN_PERCENTAGE = 0; // Minimum percentage value +const int MAX_PERCENTAGE = 100; // Maximum percentage value + +#define BAT_CHARGE_STATE 23 // LOW for charging, HIGH not charging + +class Battery { +public: + BLEService batteryBleService; + BLECharacteristic chargeCharacteristic; + BLECharacteristic isChargingCharacteristic; + float lastChargePercentage; + + Battery(); + void begin(); + void updateBatteryState(); + bool isCharging(); +}; + +Battery::Battery() { + analogReference(AR_INTERNAL_2_4); + pinMode(VBAT_ENABLE, OUTPUT); + pinMode(BAT_CHARGE_STATE, INPUT); + + batteryBleService = BLEService("1a1a1963-7e2b-45ed-a1f7-82d01423e841"); + chargeCharacteristic = + BLECharacteristic("aaa8d3ef-4c86-47a2-9428-6bdcf6041e3e"); + isChargingCharacteristic = + BLECharacteristic("d6bc7685-31c6-4fd7-843f-9e74237ca2d2"); + + digitalWrite(VBAT_ENABLE, LOW); +} + +void startCharacteristic(BLECharacteristic &characteristic) { + characteristic.setProperties(CHR_PROPS_READ | CHR_PROPS_NOTIFY); + characteristic.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + characteristic.setFixedLen(1); + characteristic.begin(); +} + +void Battery::begin() { + Bluefruit.Advertising.addService(batteryBleService); + batteryBleService.begin(); + + startCharacteristic(chargeCharacteristic); + startCharacteristic(isChargingCharacteristic); + + lastChargePercentage = 100.0; + this->chargeCharacteristic.notify8(100.0); +} + +// Function to map the battery voltage to a percentage +uint8_t mapVoltageToPercentage(float voltage) { + if (voltage < MIN_VOLTAGE) { + return -1; + } + + // My battery is kind of jank and old so the values are experimental while the + // real battery capacity and voltage remains unknown + if (voltage > MAX_VOLTAGE) { + return 100; + } + + // Map the voltage to the percentage scale + float percentage = + ((voltage - MIN_VOLTAGE) / (MAX_VOLTAGE - MIN_VOLTAGE)) * 100; + return round(percentage); +} + +bool Battery::isCharging() { return digitalRead(BAT_CHARGE_STATE) == LOW; } + +void Battery::updateBatteryState() { + unsigned int adcCount = analogRead(PIN_VBAT); + double adcVoltage = (adcCount * vRef) / numReadings; + double vBat = adcVoltage * 1510.0 / 510.0; // Voltage divider from Vbat to ADC + + uint8_t percentage = mapVoltageToPercentage(vBat); + bool isCharging = this->isCharging(); + Serial.printf("Charge %d", percentage); + + this->isChargingCharacteristic.notify8(isCharging); + + if (percentage < lastChargePercentage || isCharging) { + this->chargeCharacteristic.notify8(percentage); + Serial.println("Notified"); + lastChargePercentage = percentage; + } +} \ No newline at end of file diff --git a/windows/src/main.cpp b/windows/src/main.cpp index 39637ab..923722e 100644 --- a/windows/src/main.cpp +++ b/windows/src/main.cpp @@ -1,54 +1,146 @@ +#include "Adafruit_TinyUSB.h" #include "Adafruit_VL53L0X.h" +#include "battery.h" +#include +#define CHARGIN_CURRENT_CONTROL_PIN 13 + +SoftwareTimer mainTimer; Adafruit_VL53L0X lox = Adafruit_VL53L0X(); -#define LEDG 3 -void setup() -{ - Serial.begin(115200); - pinMode(LED_BUILTIN, OUTPUT); - pinMode(LEDG, OUTPUT); - Serial.println("Adafruit VL53L0X test"); - Serial.println(LED_BUILTIN); - if (!lox.begin()) - { - Serial.println(F("Failed to boot VL53L0X")); - while (1) - ; - } - // power - Serial.println(F("VL53L0X API Simple Ranging example\n\n")); +BLEService service("19B10000-E8F2-537E-4F6C-D104768A1214"); +BLECharacteristic characteristic("19B10000-E8F2-537E-4F6C-D104768A1214"); + +BLEDis bledis; // DIS (Device Information Service) helper class instance +Battery battery; + +void prepareBluetooth() { + Bluefruit.setName("CO2CICKA WINDOWS"); + + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + + // There is enough room for the dev name in the advertising packet + Bluefruit.Advertising.addName(); + + bledis.setManufacturer("Adafruit Industries"); + bledis.setModel("Bluefruit Feather52"); + bledis.begin(); + battery.begin(); + + Bluefruit.Advertising.addService(service); + service.begin(); + + characteristic.setProperties(CHR_PROPS_READ | CHR_PROPS_NOTIFY); + characteristic.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + characteristic.setFixedLen(1); + characteristic.begin(); + + /* Start Advertising + * - Enable auto advertising if disconnected + * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Timeout for fast mode is 30 seconds + * - Start(timeout) with timeout = 0 will advertise forever (until connected) + * + * For recommended advertising interval + * https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(false); + Bluefruit.Advertising.setInterval(500, 500); // in unit of 0.625 ms + Bluefruit.autoConnLed(false); } -void loop() -{ +void measurementCallback(TimerHandle_t xTimerID) { + xTimerReset(xTimerID, 0); + VL53L0X_RangingMeasurementData_t measure; - Serial.print("Reading a measurement... "); - lox.rangingTest(&measure, false); // pass in 'trz mue' to get debug data printout! + Serial.println("Trying to update battery service"); + battery.updateBatteryState(); + Serial.println("Updated battery service"); + + Serial.println("Reading a measurement... "); + lox.rangingTest(&measure, + false); // pass in 'trz mue' to get debug data printout! + Serial.print("Measurement done:"); - if (measure.RangeStatus != 4) - { // phase failures have incorrect data + uint8_t notify_value = 0; + + if (measure.RangeStatus != 4) { // phase failures have incorrect data Serial.print("Distance (mm): "); Serial.println(measure.RangeMilliMeter); - if (measure.RangeMilliMeter < 100) - { - digitalWrite(LED_BUILTIN, LOW); - digitalWrite(LEDG, HIGH); + bool isClosed = measure.RangeMilliMeter < 100; + notify_value = isClosed; + + if (isClosed) { + digitalWrite(LED_RED, LOW); + } else { + digitalWrite(LED_RED, HIGH); } - else - { - digitalWrite(LED_BUILTIN, HIGH); - digitalWrite(LEDG, LOW); + } else { + notify_value = false; + digitalWrite(LED_RED, HIGH); + Serial.println(" out of range "); + } + + characteristic.write8(notify_value); + + digitalWrite(LED_BLUE, LOW); + Bluefruit.Advertising.start(); + delay(5000); + digitalWrite(LED_BLUE, HIGH); + + if (Bluefruit.connected()) { + Serial.println("BLE connected, writing and waiting for disconnect"); + + while (Bluefruit.connected()) { + delay(100); + digitalWrite(LED_BLUE, !digitalRead(LED_BLUE)); } + + digitalWrite(LED_BLUE, HIGH); + } else { + Bluefruit.Advertising.stop(); } - else - { - digitalWrite(LED_BUILTIN, HIGH); - digitalWrite(LEDG, LOW); - Serial.println(" out of range "); + + xTimerStart(xTimerID, 0); + sd_app_evt_wait(); +} + +void setup() { + Serial.begin(115200); + // while (!Serial) + // ; + + pinMode(LED_RED, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + + pinMode(CHARGIN_CURRENT_CONTROL_PIN, OUTPUT); + digitalWrite(CHARGIN_CURRENT_CONTROL_PIN, HIGH); + + if (!lox.begin()) { + Serial.println(F("Failed to boot VL53L0X")); + while (1) + ; } + digitalWrite(LED_RED, LOW); + // power + Bluefruit.begin(); + Bluefruit.setTxPower(-8); // Check bluefruit.h for supported values + + prepareBluetooth(); delay(100); -} \ No newline at end of file + digitalWrite(LED_RED, HIGH); + Serial.println("Starting loop"); + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + + mainTimer.begin(30000, measurementCallback); + mainTimer.start(); + suspendLoop(); + + sd_app_evt_wait(); +} + +void loop() {}