use std::env;
use std::time::Duration;
use tasmor_lib::state::DeviceState;
use tasmor_lib::subscription::Subscribable;
use tasmor_lib::{Device, MqttBroker};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
if args.len() < 3 {
print_usage(&args[0]);
std::process::exit(1);
}
let mode = &args[1];
match mode.as_str() {
"http" => run_http_mode(&args).await,
"mqtt" => run_mqtt_punctual_mode(&args).await,
"subscribe" => run_mqtt_subscription_mode(&args).await,
_ => {
eprintln!("Unknown mode: {mode}");
print_usage(&args[0]);
std::process::exit(1);
}
}
}
fn print_usage(program: &str) {
eprintln!("Usage:");
eprintln!(" {program} http <device_ip> [username] [password]");
eprintln!(" {program} mqtt <broker_host> <device_topic> [username] [password]");
eprintln!(" {program} subscribe <broker_host> <device_topic> [username] [password]");
eprintln!();
eprintln!("Examples:");
eprintln!(" {program} http 192.168.1.100");
eprintln!(" {program} http 192.168.1.100 admin password");
eprintln!(" {program} mqtt 192.168.1.50 tasmota_plug");
eprintln!(" {program} subscribe 192.168.1.50 tasmota_plug user pass");
}
fn format_uptime(duration: Duration) -> String {
let total_secs = duration.as_secs();
let days = total_secs / 86400;
let hours = (total_secs % 86400) / 3600;
let minutes = (total_secs % 3600) / 60;
let seconds = total_secs % 60;
if days > 0 {
format!("{days}d {hours:02}:{minutes:02}:{seconds:02}")
} else {
format!("{hours:02}:{minutes:02}:{seconds:02}")
}
}
fn print_system_info(state: &DeviceState, prefix: &str) {
match state.uptime() {
Some(uptime) => {
println!("{prefix}Uptime: {}", format_uptime(uptime));
println!("{prefix} (raw: {} seconds)", uptime.as_secs());
}
None => {
println!("{prefix}Uptime: not available");
}
}
if let Some(info) = state.system_info()
&& let Some(rssi) = info.wifi_rssi()
{
println!("{prefix}WiFi RSSI: {rssi} dBm");
}
}
async fn run_http_mode(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
if args.len() < 3 {
eprintln!("HTTP mode requires: <device_ip> [username] [password]");
std::process::exit(1);
}
let device_ip = &args[2];
println!("=== HTTP Uptime Query ===");
println!("Device: {device_ip}");
println!();
let mut builder = Device::http(device_ip);
if args.len() >= 5 {
builder = builder.with_credentials(&args[3], &args[4]);
}
let (device, state) = builder.build().await?;
print_system_info(&state, "");
drop(device);
Ok(())
}
async fn run_mqtt_punctual_mode(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
if args.len() < 4 {
eprintln!("MQTT mode requires: <broker_host> <device_topic>");
std::process::exit(1);
}
let broker_host = &args[2];
let device_topic = &args[3];
println!("=== MQTT Punctual Uptime Query ===");
println!("Broker: {broker_host}");
println!("Device: {device_topic}");
println!();
let mut broker_builder = MqttBroker::builder().host(broker_host);
if args.len() >= 6 {
broker_builder = broker_builder.credentials(&args[4], &args[5]);
}
let broker = broker_builder.build().await?;
let (device, state) = broker.device(device_topic).build().await?;
print_system_info(&state, "");
device.disconnect().await;
broker.disconnect().await?;
Ok(())
}
async fn run_mqtt_subscription_mode(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
if args.len() < 4 {
eprintln!("Subscribe mode requires: <broker_host> <device_topic>");
std::process::exit(1);
}
let broker_host = &args[2];
let device_topic = &args[3];
println!("=== MQTT Subscription Mode ===");
println!("Broker: {broker_host}");
println!("Device: {device_topic}");
println!();
println!("Waiting for device connection and telemetry...");
println!("(Running for 5 minutes, or press Ctrl+C to exit)");
println!();
let mut broker_builder = MqttBroker::builder().host(broker_host);
if args.len() >= 6 {
broker_builder = broker_builder.credentials(&args[4], &args[5]);
}
let broker = broker_builder.build().await?;
let (device, initial_state) = broker.device(device_topic).build().await?;
if let Some(uptime) = initial_state.uptime() {
println!(
"[Initial] Uptime: {} ({} seconds)",
format_uptime(uptime),
uptime.as_secs()
);
}
device.on_connected(|state| {
if let Some(uptime) = state.uptime() {
println!(
"[Connected] Uptime: {} ({} seconds)",
format_uptime(uptime),
uptime.as_secs()
);
}
if let Some(info) = state.system_info()
&& let Some(rssi) = info.wifi_rssi()
{
println!("[Connected] WiFi RSSI: {rssi} dBm");
}
});
device.on_disconnected(|| {
println!("[Disconnected] Device offline");
});
let device_clone = device.clone();
device.on_reconnected(move || {
println!("[Reconnected] Connection restored, querying fresh state...");
let dev = device_clone.clone();
tokio::spawn(async move {
match dev.query_state().await {
Ok(state) => {
if let Some(uptime) = state.uptime() {
println!(
"[Refreshed] Uptime: {} ({} seconds)",
format_uptime(uptime),
uptime.as_secs()
);
}
}
Err(e) => println!("[Refreshed] Failed to query state: {e}"),
}
});
});
let device_periodic = device.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(60));
interval.tick().await;
loop {
interval.tick().await;
match device_periodic.query_state().await {
Ok(state) => {
if let Some(uptime) = state.uptime() {
println!(
"[Periodic] Uptime: {} ({} seconds)",
format_uptime(uptime),
uptime.as_secs()
);
}
}
Err(e) => println!("[Periodic] Query failed: {e}"),
}
}
});
tokio::time::sleep(Duration::from_secs(300)).await;
println!();
println!("Shutting down...");
device.disconnect().await;
broker.disconnect().await?;
Ok(())
}