use std::path::PathBuf;
use std::time::Duration;
use anyhow::{Context, Result};
use owo_colors::OwoColorize;
use serde::Serialize;
use crate::cli::OutputFormat;
use crate::format::{FormatOptions, bq_to_pci, csv_escape, format_status};
use crate::style;
use crate::util::{connect_device_with_progress, require_device_interactive, write_output};
pub async fn cmd_status(
device: Option<String>,
timeout: Duration,
format: OutputFormat,
output: Option<&PathBuf>,
opts: &FormatOptions,
brief: bool,
) -> Result<()> {
let identifier = require_device_interactive(device).await?;
let device = connect_device_with_progress(&identifier, timeout, true).await?;
let device_id = device.address().to_string();
let name = device.name().map(|s| s.to_string());
let reading_result = device
.read_current()
.await
.context("Failed to read current values");
crate::util::disconnect_device(&device).await;
let reading = reading_result?;
crate::util::save_reading_to_store(&device_id, &reading);
let device_name = name.clone().unwrap_or_else(|| identifier.clone());
let content = match format {
OutputFormat::Json => format_status_json(&device_name, &reading, opts)?,
OutputFormat::Csv => format_status_csv(&device_name, &reading, opts),
OutputFormat::Text => {
if brief {
format_status_brief(&reading, opts)
} else {
format_status_text(&device_name, &reading, opts)
}
}
};
write_output(output, &content)?;
Ok(())
}
fn format_status_text(
device_name: &str,
reading: &aranet_types::CurrentReading,
opts: &FormatOptions,
) -> String {
let status_str = format_status(reading.status, opts.no_color);
let temp = opts.format_temp(reading.temperature);
let name_display = if opts.no_color {
device_name.to_string()
} else {
format!("{}", device_name.cyan())
};
if reading.co2 > 0 {
let co2_display = style::format_co2_colored(reading.co2, opts.no_color);
let humidity_display = style::format_humidity_colored(reading.humidity, opts.no_color);
format!(
"{}: {} ppm {} {} {} {:.1}hPa\n",
name_display, co2_display, status_str, temp, humidity_display, reading.pressure
)
} else if let Some(radon) = reading.radon {
let radon_display = style::format_radon_colored(radon, opts.no_color);
let humidity_display = style::format_humidity_colored(reading.humidity, opts.no_color);
format!(
"{}: {} {} {} {} {} {:.1}hPa\n",
name_display,
radon_display,
opts.radon_display_unit(),
status_str,
temp,
humidity_display,
reading.pressure
)
} else if let Some(rate) = reading.radiation_rate {
format!("{}: {:.3} uSv/h\n", name_display, rate)
} else {
let humidity_display = style::format_humidity_colored(reading.humidity, opts.no_color);
format!("{}: {} {}\n", name_display, temp, humidity_display)
}
}
fn format_status_brief(reading: &aranet_types::CurrentReading, opts: &FormatOptions) -> String {
if reading.co2 > 0 {
format!("{}\n", reading.co2)
} else if let Some(radon) = reading.radon {
if opts.bq {
format!("{}\n", radon)
} else {
format!("{:.2}\n", bq_to_pci(radon))
}
} else if let Some(rate) = reading.radiation_rate {
format!("{:.3}\n", rate)
} else {
format!("{:.1},{}\n", reading.temperature, reading.humidity)
}
}
fn format_status_json(
device_name: &str,
reading: &aranet_types::CurrentReading,
opts: &FormatOptions,
) -> Result<String> {
#[derive(Serialize)]
struct StatusJson<'a> {
device: &'a str,
co2: u16,
temperature: f32,
temperature_unit: &'static str,
humidity: u8,
pressure: f32,
battery: u8,
status: String,
#[serde(skip_serializing_if = "Option::is_none")]
radon_bq: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
radon_pci: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
radiation_rate: Option<f32>,
}
let json = StatusJson {
device: device_name,
co2: reading.co2,
temperature: opts.convert_temp(reading.temperature),
temperature_unit: if opts.fahrenheit { "F" } else { "C" },
humidity: reading.humidity,
pressure: reading.pressure,
battery: reading.battery,
status: format!("{:?}", reading.status),
radon_bq: reading.radon,
radon_pci: reading.radon.map(bq_to_pci),
radiation_rate: reading.radiation_rate,
};
opts.as_json(&json)
}
fn format_status_csv(
device_name: &str,
reading: &aranet_types::CurrentReading,
opts: &FormatOptions,
) -> String {
let temp_header = if opts.fahrenheit {
"temperature_f"
} else {
"temperature_c"
};
let radon_value = reading
.radon
.map(|r| format!("{:.2}", opts.convert_radon(r)))
.unwrap_or_default();
if opts.no_header {
format!(
"{},{},{:.1},{},{:.1},{},{:?},{},{}\n",
csv_escape(device_name),
reading.co2,
opts.convert_temp(reading.temperature),
reading.humidity,
reading.pressure,
reading.battery,
reading.status,
radon_value,
reading
.radiation_rate
.map(|r| format!("{:.3}", r))
.unwrap_or_default()
)
} else {
format!(
"device,co2,{},humidity,pressure,battery,status,{},radiation_usvh\n\
{},{},{:.1},{},{:.1},{},{:?},{},{}\n",
temp_header,
opts.radon_csv_header(),
csv_escape(device_name),
reading.co2,
opts.convert_temp(reading.temperature),
reading.humidity,
reading.pressure,
reading.battery,
reading.status,
radon_value,
reading
.radiation_rate
.map(|r| format!("{:.3}", r))
.unwrap_or_default()
)
}
}