1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
//! # Foxess API client library
//! The foxess crate implements a subset of available [FoxESS Cloud APIs].
//!
//! Its purpose is mainly focused on APIs that help in executing automatic scheduling of battery charging and battery discharging (self-use) given external data such as tariffs from e.g.:
//! * [Nordpool] in the Nordic European region or some other supplier of daily tariffs.
//! * Weather temperature forecast data to estimate household power consumption
//! * Weather cloud forecast data and sun incidence calculations to estimate PV power production
//! * Etc. depending on level of ambition/precision in estimates
//!
//! The APIs are tested for a Fox H3 model SK-HWR-12, and although the FoxESS Cloud APIs are general,
//! settings and variables are not guaranteed to be fully supported by all inverters.
//!
//! ## License
//! This library comes with a standard [MIT license]
//!
//! ## Features
//! * `async` (default) - Enables async requests using [reqwest](https://crates.io/crates/reqwest)
//! * `blocking` - Enables blocking requests using [reqwest](https://crates.io/crates/reqwest)
//!
//! `async` and `blocking` features are mutually exclusive, and since `async` is default, one must declare
//! default-features = false when enabling `blocking`
//!
//! ## Blocking mode (alternative)
//! This crate defaults to the `async` feature, and the documentation on docs.rs is generated for
//! the async API.
//!
//! If you prefer a blocking API, disable default features and enable `blocking` instead:
//! ```toml
//! [dependencies]
//! foxess = { version = "0.x.y", default-features = false, features = ["blocking"] }
//! ```
//!
//! The blocking API uses the same `Fox` type name, but methods are synchronous (no `.await`).
//!
//! ## Usage Overview
//! Note down your inverter serial number, can be found from within the FoxCloud2.0 app or the [FoxESS Cloud V2 site] web site.
//!
//! Get an API key, it can be retrieved from the [FoxESS Cloud V1 site] under User Profile/API Management.
//!
//! Decide whether to use the blocking or non-blocking feature in cargo.toml dependencies
//! ```toml
//! [dependencies]
//! # Non-blocking (async)
//! foxess = "0.x.y"
//!
//! # Non-blocking (async) if you want clarity
//! foxess = { version = "0.x.y", features = ["async"] }
//!
//! # Blocking
//! foxess = { version = "0.x.y", default-features = false, features = ["blocking"] }
//! ```
//!
//! ## Examples
//! ### Non-blocking request for battery State of Charge
//! ```rust,no_run
//! use foxess::Fox;
//! use foxess::fox_variables::SoC;
//!
//! # let rt = tokio::runtime::Runtime::new().unwrap();
//! # rt.block_on(async {
//! let api_key = "my_api_key";
//! let sn = "my_inverter_sn";
//!
//! let fox = Fox::new(api_key, sn, 30).unwrap();
//! let soc = fox.get_variable_typed::<SoC>().await.unwrap();
//!
//! println!("Current battery State of Charge: {}%", soc);
//! # });
//! ```
//! ### Non-blocking request for one-day PV power (Photo Voltaic), loads power (household load) and battery SoC
//! ```rust,no_run
//! use std::ops::Add;
//! use chrono::{TimeZone, Utc, Local, Duration};
//! use foxess::{Fox, FoxVariables};
//!
//! # let rt = tokio::runtime::Runtime::new().unwrap();
//! # rt.block_on(async {
//! let api_key = "my_api_key";
//! let sn = "my_inverter_sn";
//!
//! let start = Local.with_ymd_and_hms(2026, 2, 1, 0, 0, 0).unwrap().with_timezone(&Utc);
//! let end = start.add(Duration::days(1));
//! let variables = vec![FoxVariables::PvPower, FoxVariables::LoadsPower, FoxVariables::SoC];
//!
//! let fox = Fox::new(api_key, sn, 30).unwrap();
//! let history = fox.get_variables_history(start, end, variables).await.unwrap();
//!
//! let pv_history = history.get(FoxVariables::PvPower);
//! let loads_history = history.get(FoxVariables::LoadsPower);
//! let soc_history = history.get_u8_percent(FoxVariables::SoC);
//! let soh_history = history.get_u8_percent(FoxVariables::SOH);
//!
//! // Expect these to have values
//! assert!(pv_history.is_some());
//! assert!(loads_history.is_some());
//! assert!(soc_history.is_some());
//!
//! // Expect this to be None
//! assert!(soh_history.is_none());
//!
//! if let Some(first) = pv_history.unwrap().first() {
//! println!("PV power: {} W at {:?}", first.data, first.date_time);
//! }
//! # });
//! ```
//! ### Adding own marker structs for variables and settings
//! This is handy if an available variable or setting hasn't got a builtin marker struct yet
//! for typed operations. Or if an available builtin marker struct hasn't sufficient guards for
//! a settable setting.
//!
//! Be aware, though, that not all available variables and settings work with all inverters.
//! Fox will throw an error if FoxESS Cloud doesn't support the requested variable or setting.
//!
//! ```rust, no_run
//! use foxess::{Fox, FoxError, FoxVariables, FoxSettings};
//! use foxess::fox_variables::VariableSpec;
//! use foxess::fox_settings::{SettingSpec, SettableSettingSpec};
//!
//! # let rt = tokio::runtime::Runtime::new().unwrap();
//! # rt.block_on(async {
//! let api_key = "my_api_key";
//! let sn = "my_inverter_sn";
//!
//! let fox = Fox::new(&api_key, &sn, 30).unwrap();
//! let rf = fox.get_variable_typed::<RFreq>().await.unwrap();
//! println!("Current R frequency state: {}", rf);
//!
//! let apl = fox.get_setting_typed::<MaxSetDischargeCurrent>().await.unwrap();
//! println!("Current max discharge current: {} A", apl);
//!
//! let _ = fox.set_setting_typed::<MaxSetDischargeCurrent>(20.0).await.unwrap();
//!
//! # });
//!
//! struct RFreq;
//! impl VariableSpec for RFreq {
//! type Value = String;
//!
//! const VARIABLE: FoxVariables = FoxVariables::RFreq;
//!
//! fn into(raw: f64) -> Result<Self::Value, FoxError> {
//! Ok(format!("{} Hz", raw))
//! }
//! }
//!
//! struct MaxSetDischargeCurrent;
//! impl SettingSpec for MaxSetDischargeCurrent {
//! type Value = f64;
//!
//! const SETTING: FoxSettings = FoxSettings::MaxSetDischargeCurrent;
//!
//! fn parse(raw: String) -> Result<Self::Value, FoxError> {
//! Ok(raw.parse::<f64>().unwrap())
//! }
//! }
//!
//! impl SettableSettingSpec for MaxSetDischargeCurrent {
//! fn format(value: &Self::Value) -> String {
//! value.clamp(3.0, 26.0).to_string()
//! }
//! }
//! ```
//!
//! [MIT license]: https://github.com/gostonefire/foxess/blob/main/LICENSE
//! [FoxESS Cloud APIs]: https://www.foxesscloud.com/public/i18n/en/OpenApiDocument.html
//! [Nordpool]: https://data.nordpoolgroup.com/auction/day-ahead/prices
//! [FoxESS Cloud V2 site]: https://www.foxesscloud.com/v2
//! [FoxESS Cloud V1 site]: https://www.foxesscloud.com/user/center
compile_error!;
compile_error!;
pub use Fox;
pub use FoxError;
pub use FoxVariables;
pub use FoxSettings;
pub use FoxWorkModes;
pub use ;
pub use ;
pub use ;