Add API struct

This commit is contained in:
Xiretza 2024-02-02 15:27:46 +00:00
parent 5ec9079456
commit 1a4694bad5
2 changed files with 88 additions and 30 deletions

View file

@ -1,6 +1,7 @@
use reqwest::{blocking::Client, Url};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use std::{collections::HashMap, net::IpAddr}; use std::{borrow::Borrow, collections::HashMap, net::IpAddr};
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -17,6 +18,80 @@ pub enum Error {
Response(Status), Response(Status),
} }
pub struct Fronius {
client: Client,
base_url: Url,
}
impl Fronius {
pub fn connect(ip: IpAddr) -> Result<Self, Error> {
let client = Client::new();
let mut url = reqwest::Url::parse("http://placeholder.local/solar_api/GetAPIVersion.cgi")
.expect("Initial base URL should be valid");
url.set_ip_host(ip)
.expect("Base URL should be a valid base");
let api_version: ApiVersion = client.get(url.clone()).send()?.json()?;
if api_version.api_version != 1 {
return Err(Error::UnsupportedApiVersion(api_version.api_version));
}
url.set_path(&api_version.base_url);
Ok(Self {
client,
base_url: url,
})
}
fn make_request_inner(&self, url: Url) -> Result<serde_json::Value, Error> {
let response: FroniousResponse<serde_json::Value> = self.client.get(url).send()?.json()?;
if response.head.status.code != StatusCode::Okay {
return Err(Error::Response(response.head.status));
}
Ok(response.body)
}
pub fn make_request<T, I, K, V>(&self, endpoint: &str, params: I) -> Result<T, Error>
where
T: DeserializeOwned,
I: IntoIterator,
I::Item: Borrow<(K, V)>,
K: AsRef<str>,
V: AsRef<str>,
{
let mut url = self
.base_url
.join(endpoint)
.map_err(|_e| Error::InvalidEndpoint(endpoint.to_string()))?;
url.query_pairs_mut().extend_pairs(params);
let body = self.make_request_inner(url)?;
Ok(T::deserialize(body)?)
}
pub fn get_inverter_realtime_data_device<C: DataCollection>(
&self,
device_id: DeviceId,
) -> Result<C, Error> {
let device_id = u8::from(device_id).to_string();
let response: CommonResponseBody<_> = self.make_request(
"GetInverterRealtimeData.cgi",
[
("Scope", "Device"),
("DeviceId", &device_id),
("DataCollection", C::param_value()),
],
)?;
Ok(response.data)
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct FroniousResponse<T> { pub struct FroniousResponse<T> {
@ -24,7 +99,7 @@ pub struct FroniousResponse<T> {
body: T, body: T,
} }
#[derive(Debug, Serialize_repr, Deserialize_repr)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(u8)] #[repr(u8)]
pub enum StatusCode { pub enum StatusCode {
Okay = 0, Okay = 0,
@ -82,20 +157,14 @@ pub struct UnitAndValues<T> {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct ApiVersion { struct ApiVersion {
#[serde(rename = "APIVersion")] #[serde(rename = "APIVersion")]
api_version: i8, api_version: u64,
#[serde(rename = "BaseURL")] #[serde(rename = "BaseURL")]
base_url: String, base_url: String,
compatibility_range: String, compatibility_range: String,
} }
pub fn get_api_version(ip: IpAddr) -> Result<ApiVersion, Box<dyn std::error::Error>> {
let mut url = reqwest::Url::parse("http://placeholder.local/solar_api/GetAPIVersion.cgi")?;
let _ = url.set_ip_host(ip);
Ok(reqwest::blocking::Client::new().get(url).send()?.json()?)
}
pub struct DeviceId(u8); pub struct DeviceId(u8);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
@ -141,22 +210,3 @@ impl DataCollection for CumulationInverterData {
"CumulationInverterData" "CumulationInverterData"
} }
} }
pub fn get_inverter_realtime_data_device<C: DataCollection>(
ip: IpAddr,
device_id: DeviceId,
) -> Result<FroniousResponse<CommonResponseBody<C>>, Box<dyn std::error::Error>> {
let device_id = u8::from(device_id).to_string();
let params = [
("Scope", "Device"),
("DeviceId", &device_id),
("DataCollection", C::param_value()),
];
let mut url = reqwest::Url::parse_with_params(
"http://placeholder.local/solar_api/v1/GetInverterRealtimeData.cgi",
&params,
)?;
let _ = url.set_ip_host(ip);
Ok(reqwest::blocking::Client::new().get(url).send()?.json()?)
}

View file

@ -1,9 +1,17 @@
use std::net::IpAddr; use std::net::IpAddr;
use fronious::{CumulationInverterData, DeviceId, Fronius};
mod fronious; mod fronious;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let ip = IpAddr::V4(std::net::Ipv4Addr::new(10, 69, 0, 50)); let ip = IpAddr::V4(std::net::Ipv4Addr::new(10, 69, 0, 50));
println!("{:#?}", fronious::get_api_version(ip)?); let fronius = Fronius::connect(ip)?;
println!(
"{:?}",
fronius.get_inverter_realtime_data_device::<CumulationInverterData>(
DeviceId::try_from(0).unwrap(),
)?
);
//println!("{:#?}", fronious::get_inverter_realtime_data(ip, fronious::Scope::System)?); //println!("{:#?}", fronious::get_inverter_realtime_data(ip, fronious::Scope::System)?);
Ok(()) Ok(())
} }