use jiff::{Zoned, tz::TimeZone};
use pvoutput_client::{
ClientError,
client::Client,
types::params::{
Aggregation, GetExtendedParams, GetInsolationParams, GetOutputParams, GetStatisticsParams, GetStatusParams,
GetSystemParams,
},
};
use rand::random_bool;
#[tokio::main]
#[allow(clippy::cast_precision_loss, clippy::too_many_lines)]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv()?;
let (Ok(api_key), Ok(system)) = (std::env::var("PVO_API_KEY"), std::env::var("PVO_SYSTEM")) else {
panic!("PVO_API_KEY and PVO_SYSTEM must be set! Use .env file or export them.")
};
let Ok(system) = system.parse() else {
panic!("PVO_SYSTEM must be a valid system ID!")
};
let client = Client::new(&api_key, system, None, None)?;
let params = GetSystemParams {
donations: random_bool(0.5),
teams: random_bool(0.5),
monthly_estimates: random_bool(0.5),
extended_data: random_bool(0.5),
secondary_array: random_bool(0.5),
tariffs: random_bool(0.5),
..Default::default()
};
let full_system = client.get_system(params).await?.data;
let system = &full_system.system;
println!("Retrieving data for {} ({} system)...", system.name, format_watts(system.size as f32));
let status = client.get_status(GetStatusParams::default()).await?;
let status = status.data;
println!(
"On {} at {}, {} generated {} of power.",
status.date,
status.time,
system.name,
format_watts(status.power_generation)
);
let daily_status = client.get_status_daily(GetStatusParams::default()).await?.data;
println!(
"On this system's most recent productive day, power generation peaked at {} with a value of {}!",
daily_status.peak_time,
format_watts(daily_status.peak_power_generation)
);
let history = client.get_status_history(GetStatusParams::default()).await?.data;
println!(
"Found {} history entries; latest at {}, with energy efficiency of {:.2} kWh/kWh",
history.len(),
history[0].time,
history[0].energy_efficiency
);
let stats = client.get_statistics(GetStatisticsParams::default()).await?.data;
println!(
"{} generated {}h of energy between {} and {}!",
system.name,
format_watts(stats.energy_generated as f32),
stats.date_from,
stats.date_to
);
let outputs = client.get_outputs(GetOutputParams::default()).await?.data;
println!("Weather for {} was {:?}", outputs[0].date, outputs[0].conditions);
let aggregate = client.get_aggregate_outputs(Aggregation::Yearly, GetOutputParams::default()).await?.data;
println!(
"{} generated {} in {}",
system.name,
format_watts(aggregate[0].energy_generation as f32),
aggregate[0].date.year()
);
print!("Latest extended data: ");
let extended = client.get_extended(GetExtendedParams::default()).await;
if matches!(extended, Err(ClientError::NotFound)) {
println!("none!");
} else {
println!("{:?}", extended?.data);
}
let missing = client.get_missing(None, None).await?.data;
if missing.is_empty() {
println!("No missing outputs :)");
} else {
println!("Found {} days with missing outputs, dating back to {}.", missing.len(), missing[0]);
}
let insolation = client.get_insolation(GetInsolationParams::default()).await;
if let Ok(insolation) = insolation {
println!("Retrieved {} insolation results.", insolation.data.len());
} else if !matches!(insolation, Err(ClientError::DonationModeRequired)) {
insolation?;
}
println!();
let favourites = client.get_favourites(None).await?.data;
println!(
"{} has {} favourite systems{}",
system.name,
favourites.len(),
if favourites.is_empty() { "." } else { ":" }
);
for favourite in favourites {
println!(
" - {} (ID {}): {} system",
favourite.system.name,
favourite.id,
format_watts(favourite.system.size as f32)
);
}
println!();
let team_id = full_system.teams.first().copied().unwrap_or(1);
let team = client.get_team(team_id).await?.data;
println!(
"Team {} has generated {} across {} outputs from {} systems!",
team.name,
format_watts(team.energy_generation as f32),
team.outputs,
team.systems
);
let ladder = client.get_ladder(None).await?;
println!();
println!("Ladder position as of {}:", ladder.data.date);
if let Some(rank) = ladder.data.generation_rank {
println!(" - Generation rank: {rank}. View online at {}", ladder.data.generation_rank_url().unwrap());
}
println!(" - Efficiency rank: {}", ladder.data.efficiency_rank.unwrap_or_default());
println!(" - Last output considered: {}", ladder.data.last_output.unwrap_or_default());
println!();
let reset = Zoned::new(ladder.rate_limit.reset, TimeZone::system());
println!(
"Rate limit: {} per hour, {} remaining, resetting at {} (in {} minutes)",
ladder.rate_limit.limit,
ladder.rate_limit.remaining,
reset,
Zoned::now().until(&reset).unwrap().get_minutes()
);
Ok(())
}
fn format_watts(watts: f32) -> String {
match watts {
w if w.is_nan() => String::from("zero W"),
..1000.0 => format!("{watts:.2} W"),
..1_000_000.0 => format!("{:.2} kW", watts / 1000.0),
..1_000_000_000.0 => format!("{:.2} MW", watts / 1_000_000.0),
_ => format!("{:.2} GW", watts / 1_000_000_000.0),
}
}