pvoutput_client/types/
params.rs

1//! Parameters for the various endpoints.
2
3use std::{collections::HashMap, fmt::Display};
4
5use jiff::civil::{Date, Time};
6use serde::{Serialize, Serializer};
7use serde_repr::Serialize_repr;
8
9use crate::types::Conditions;
10
11/// Parameters for the [`get_status`](crate::Client::get_status) endpoint.
12#[derive(Debug, Serialize, Clone, Default)]
13pub struct GetStatusParams {
14	/// Date to retrieve status for.
15	#[serde(rename = "d", serialize_with = "jiff_ymd")]
16	pub date: Option<Date>,
17
18	/// Time to retrieve status for.
19	#[serde(rename = "t", serialize_with = "jiff_hhmm")]
20	pub time: Option<Time>,
21
22	/// Return [history](crate::Client::get_status_history) results in ascending order.
23	#[serde(rename = "asc", serialize_with = "bool_int")]
24	pub ascending: bool,
25
26	/// Number of results to return when making a [history request](crate::Client::get_status_history), up to a maximum
27	/// of **288** (one day of data).
28	///
29	/// Defaults to **30**.
30	pub limit: Option<u16>,
31
32	/// If set, only statuses after this time will be returned.
33	#[serde(serialize_with = "jiff_hhmm")]
34	pub from: Option<Time>,
35
36	/// If set, only statuses before this time will be returned.
37	#[serde(serialize_with = "jiff_hhmm")]
38	pub to: Option<Time>,
39
40	/// Return extended data.
41	///
42	/// Requires [donation status](https://pvoutput.org/help/donations.html).
43	#[serde(rename = "ext", serialize_with = "bool_int")]
44	pub extended_data: bool,
45
46	/// System ID to retrieve status for.
47	/// If unset, the [`Client`](crate::Client)'s authenticated system is used.
48	///
49	/// Requires [donation status](https://pvoutput.org/help/donations.html).
50	#[serde(rename = "sid1")]
51	pub system_id: Option<u32>,
52}
53
54#[derive(Debug, Serialize, Clone, Default)]
55pub(crate) struct GetStatusParamsOuter {
56	#[serde(flatten)]
57	pub params: GetStatusParams,
58	#[serde(serialize_with = "bool_int")]
59	pub h: bool,
60	#[serde(serialize_with = "bool_int")]
61	pub stats: bool,
62}
63
64impl GetStatusParamsOuter {
65	pub const fn new(params: GetStatusParams, h: bool, stats: bool) -> Self { Self { params, h, stats } }
66}
67
68/// Parameters for the [`get_statistics`](crate::Client::get_statistics) endpoint.
69#[derive(Debug, Serialize, Clone, Default)]
70pub struct GetStatisticsParams {
71	/// Date to measure statistics from.
72	#[serde(rename = "df")]
73	pub date_from: Option<Date>,
74
75	/// Date to measure statistics to.
76	#[serde(rename = "dt")]
77	pub date_to: Option<Date>,
78
79	/// Return consumption data.
80	///
81	/// This information is only available to the system owner.
82	/// Requesting data for another system via the `system_id` parameter will not work.
83	#[serde(rename = "c", serialize_with = "bool_int")]
84	pub consumption: bool,
85
86	/// Return credit and debit data.
87	///
88	/// This information is only available to the system owner.
89	/// Requesting data for another system via the `system_id` parameter will not work.
90	#[serde(rename = "crdr", serialize_with = "bool_int")]
91	pub credit_and_debit: bool,
92
93	/// System ID to retrieve statistics for.
94	/// If unset, the [`Client`](crate::Client)'s authenticated system is used.
95	///
96	/// Requires [donation status](https://pvoutput.org/help/donations.html).
97	#[serde(rename = "sid1")]
98	pub system_id: Option<u32>,
99}
100
101/// Parameters for the [`get_system`](crate::Client::get_system) endpoint.
102#[derive(Debug, Serialize, Clone, Default)]
103#[allow(clippy::struct_excessive_bools)]
104pub struct GetSystemParams {
105	/// Return information about the system's secondary array if one is present.
106	#[serde(rename = "array2", serialize_with = "bool_int")]
107	pub secondary_array: bool,
108
109	/// Return information about the system's tariffs.
110	#[serde(serialize_with = "bool_int")]
111	pub tariffs: bool,
112
113	/// Return a list of the system's teams.
114	#[serde(serialize_with = "bool_int")]
115	pub teams: bool,
116
117	/// Return monthly estimates for the system.
118	#[serde(rename = "est", serialize_with = "bool_int")]
119	pub monthly_estimates: bool,
120
121	/// Return the system's donation count.
122	#[serde(serialize_with = "bool_int")]
123	pub donations: bool,
124
125	/// System ID to retrieve information for.
126	/// If unset, the [`Client`](crate::Client)'s authenticated system is used.
127	///
128	/// Requires [donation status](https://pvoutput.org/help/donations.html).
129	pub system_id: Option<u32>,
130
131	/// Return extended data.
132	#[serde(rename = "ext", serialize_with = "bool_int")]
133	pub extended_data: bool,
134}
135
136impl GetSystemParams {
137	/// Creates a new [`GetSystemParams`] with all fields set to `true`.
138	#[must_use]
139	pub const fn all() -> Self {
140		Self {
141			secondary_array: true,
142			tariffs: true,
143			teams: true,
144			monthly_estimates: true,
145			donations: true,
146			system_id: None,
147			extended_data: true,
148		}
149	}
150
151	/// Creates a new [`GetSystemParams`] for the given system with all fields set to `true`.
152	///
153	/// Requires [donation status](https://pvoutput.org/help/donations.html).
154	#[must_use]
155	pub const fn all_for_system(system: u32) -> Self {
156		Self {
157			system_id: Some(system),
158			..Self::all()
159		}
160	}
161}
162
163/// Parameters for the [`get_outputs`](crate::Client::get_outputs),
164/// [`get_aggregate_outputs`](crate::Client::get_aggregate_outputs),
165/// and [`get_team_outputs`](crate::Client::get_team_outputs) endpoints.
166#[derive(Debug, Serialize, Clone, Default)]
167pub struct GetOutputParams {
168	/// Date to retrieve outputs from.
169	#[serde(rename = "df", serialize_with = "jiff_ymd")]
170	pub date_from: Option<Date>,
171
172	/// Date to retrieve outputs to.
173	#[serde(rename = "dt", serialize_with = "jiff_ymd")]
174	pub date_to: Option<Date>,
175
176	/// Maximum number of outputs to retrieve.
177	/// Limited to **50** for non-donors and **150** for [donors](https://pvoutput.org/help/donations.html).
178	pub limit: Option<u8>,
179
180	/// System ID to retrieve outputs for.
181	/// If unset, the [`Client`](crate::Client)'s authenticated system is used.
182	///
183	/// Requires [donation status](https://pvoutput.org/help/donations.html).
184	#[serde(rename = "sid1")]
185	pub system_id: Option<u32>,
186
187	/// Retrieve insolation data.
188	/// Not supported for [team](crate::Client::get_team_outputs) or
189	/// [aggregate outputs](crate::Client::get_aggregate_outputs).
190	///
191	/// Requires [donation status](https://pvoutput.org/help/donations.html).
192	#[serde(serialize_with = "bool_int")]
193	pub insolation: bool,
194
195	/// Retrieve time of export data.
196	/// Not supported for [team outputs](crate::Client::get_team_outputs).
197	///
198	/// Requires [donation status](https://pvoutput.org/help/donations.html).
199	#[serde(rename = "timeofexport", serialize_with = "bool_int")]
200	pub time_of_export: bool,
201}
202
203/// Aggregation level for [`get_aggregate_outputs`](crate::Client::get_aggregate_outputs).
204#[derive(Copy, Clone, Debug, Serialize, PartialEq, Eq, Hash)]
205pub enum Aggregation {
206	#[serde(rename = "m")]
207	Monthly,
208
209	#[serde(rename = "y")]
210	Yearly,
211}
212
213#[derive(Debug, Serialize, Clone, Default)]
214pub(crate) struct GetOutputParamsOuter {
215	#[serde(flatten)]
216	pub params: GetOutputParams,
217
218	pub a: Option<Aggregation>,
219	pub tid: Option<u32>,
220}
221
222/// Parameters for the [`get_extended`](crate::Client::get_extended) endpoint.
223#[derive(Debug, Serialize, Clone, Default)]
224pub struct GetExtendedParams {
225	/// Start of the date range to retrieve extended data for.
226	#[serde(rename = "df")]
227	pub date_from: Option<Date>,
228
229	/// End of the date range to retrieve extended data for.
230	#[serde(rename = "dt")]
231	pub date_to: Option<Date>,
232
233	/// Maximum number of outputs to retrieve. Limited to **fifty**.
234	pub limit: Option<u8>,
235}
236
237/// Parameters for the [`get_insolation`](crate::Client::get_insolation) endpoint.
238#[derive(Debug, Serialize, Clone, Default)]
239pub struct GetInsolationParams {
240	/// Date to retrieve insolation data for.
241	///
242	/// If not set, today's date will be used.
243	#[serde(rename = "d", serialize_with = "jiff_ymd")]
244	pub date: Option<Date>,
245
246	/// Timezone to use for insolation calculation.
247	///
248	/// If not set, the system's timezone will be used.
249	/// If the system doesn't have a timezone, UTC will be used as a fallback.
250	#[serde(rename = "tz")]
251	pub timezone: Option<String>,
252
253	/// Latitude and longitude to use for insolation calculation.
254	///
255	/// If not set, the system's latitude and longitude will be used.
256	/// If the system doesn't have location information, the system's postcode's latitude and longitude will be used.
257	#[serde(rename = "ll", serialize_with = "lat_long")]
258	pub location: Option<(f32, f32)>,
259
260	/// System to retrieve insolation data for.
261	///
262	/// If unset, the [`Client`](crate::Client)'s authenticated system is used.
263	#[serde(rename = "sid1")]
264	pub system_id: Option<u32>,
265}
266
267/// Parameters for the [`search`](crate::Client::search) endpoint.
268#[derive(Debug, Serialize, Clone)]
269pub struct SearchParams {
270	/// Search query.
271	///
272	/// See the [PVOutput docs](https://pvoutput.org/help/searching.html#searching) for further information.
273	#[serde(rename = "q")]
274	pub query: String,
275
276	/// Latitude and longitude to use as the point of origin for
277	/// [distance searches](https://pvoutput.org/help/searching.html#:~:text=Distance%20(System)).
278	#[serde(rename = "ll", serialize_with = "lat_long")]
279	pub location: Option<(f32, f32)>,
280
281	/// Return country names alongside postcodes.
282	#[serde(rename = "country", serialize_with = "bool_int")]
283	pub country_names: bool,
284
285	/// Filter results by country code.
286	///
287	/// Required for postcode distance searches (e.g. `q=4000 20km`)
288	pub country_code: Option<CountryCode>,
289
290	/// Only return systems that were last seen this number of days ago or fewer.
291	pub seen: Option<u32>,
292}
293
294impl SearchParams {
295	/// Create a new search query.
296	///
297	/// `location`, `country_code`, and `seen` will be set to `None`, and `country_names` will be set to `false`.
298	pub fn new(query: impl Into<String>) -> Self {
299		Self {
300			query: query.into(),
301			location: None,
302			country_names: false,
303			country_code: None,
304			seen: None,
305		}
306	}
307}
308
309/// Country codes for use with [`SearchParams`].
310// documented here: https://pvoutput.org/help/api_specification.html#country-codes
311#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq, Hash)]
312pub enum CountryCode {
313	/// Australia.
314	#[serde(rename = "au")]
315	Australia,
316
317	/// Belgium (Belgiƫ).
318	#[serde(rename = "be")]
319	Belgium,
320
321	/// Denmark (Danmark).
322	#[serde(rename = "dk")]
323	Denmark,
324
325	/// Italy (Italia).
326	#[serde(rename = "it")]
327	Italy,
328
329	/// The Netherlands (Nederland).
330	#[serde(rename = "nl")]
331	Netherlands,
332
333	/// The United Kingdom of Great Britain and Northern Ireland.
334	#[serde(rename = "uk")]
335	UnitedKingdom,
336
337	/// The United States of America.
338	#[serde(rename = "us")]
339	UnitedStates,
340}
341
342/// Parameters for the [`add_outputs`](crate::Client::add_outputs) endpoint.
343///
344/// # Example
345/// [`AddOutputParams::new`] can be used to create a parameter struct with all other fields set to `None`.
346/// This can be used to easily create parameters with only one or two fields set:
347/// ```rust
348/// use pvoutput_client::types::{Conditions, params::AddOutputParams};
349/// use jiff::civil::Date;
350///
351/// let params = AddOutputParams {
352///   generated: Some(1_500),
353///   conditions: Some(Conditions::Cloudy),
354///   ..AddOutputParams::new(Date::constant(2025, 09, 22))
355/// };
356/// ```
357#[derive(Serialize, Clone, Debug)]
358pub struct AddOutputParams {
359	/// Output date.
360	///
361	/// Must be after 2000-01-01 and not in the future.
362	#[serde(serialize_with = "jiff_ymd_direct")]
363	pub date: Date,
364
365	/// Power generated in watt-hours.
366	/// This is *required* if an output does not yet exist for this date.
367	pub generated: Option<u32>,
368
369	/// Power exported in watt-hours.
370	pub exported: Option<u32>,
371
372	/// Peak power output in watts.
373	pub peak: Option<u32>,
374
375	/// The time at which the peak output was recorded.
376	/// Seconds will be truncated -- `11:15:05` becomes `11:15`.
377	#[serde(serialize_with = "jiff_hhmm")]
378	pub peak_time: Option<Time>,
379
380	/// The day's weather conditions.
381	pub conditions: Option<Conditions>,
382
383	/// The minimum recorded temperature in degrees Celsius.
384	///
385	/// If set, a maximum temperature must also be provided, and the minimum temperature must be lower than the maximum
386	/// temperature.
387	///
388	/// Both temperatures must be between -100.0 and 100.0.
389	pub min_temp: Option<f32>,
390
391	/// The maximum recorded temperature in degrees Celsius.
392	///
393	/// If set, a minimum temperature must also be provided, and the minimum temperature must be lower than the maximum
394	/// temperature.
395	///
396	/// Both temperatures must be between -100.0 and 100.0.
397	pub max_temp: Option<f32>,
398
399	/// A comment.
400	// TODO: handle semicolons and newlines in comments
401	pub comment: Option<String>,
402
403	/// The amount of energy imported during the peak tariff in watt-hours.
404	pub import_peak: Option<u32>,
405
406	/// The amount of energy imported during the off-peak tariff in watt-hours.
407	pub import_off_peak: Option<u32>,
408
409	/// The amount of energy imported during the shoulder tariff in watt-hours.
410	pub import_shoulder: Option<u32>,
411
412	/// The amount of energy imported during the high-shoulder tariff in watt-hours.
413	pub import_high_shoulder: Option<u32>,
414
415	/// Energy consumption in watt-hours.
416	///
417	/// Limited to 999,999,999 Wh.
418	pub consumption: Option<u32>,
419
420	/// The amount of energy exported during the peak tariff in watt-hours.
421	pub export_peak: Option<u32>,
422
423	/// The amount of energy exported during the off-peak tariff in watt-hours.
424	pub export_off_peak: Option<u32>,
425
426	/// The amount of energy exported during the shoulder tariff in watt-hours.
427	pub export_shoulder: Option<u32>,
428
429	/// The amount of energy exported during the high-shoulder tariff in watt-hours.
430	pub export_high_shoulder: Option<u32>,
431}
432
433impl AddOutputParams {
434	/// Creates a new [`AddOutputParams`] for the given date with all other fields set to `None`.
435	#[must_use]
436	pub const fn new(date: Date) -> Self {
437		Self {
438			date,
439			generated: None,
440			exported: None,
441			peak: None,
442			peak_time: None,
443			conditions: None,
444			min_temp: None,
445			max_temp: None,
446			comment: None,
447			import_peak: None,
448			import_off_peak: None,
449			import_shoulder: None,
450			import_high_shoulder: None,
451			consumption: None,
452			export_peak: None,
453			export_off_peak: None,
454			export_shoulder: None,
455			export_high_shoulder: None,
456		}
457	}
458}
459
460/// Common parameters for the [`add_status`](crate::Client::add_status),
461/// [`add_batch_status`](crate::Client::add_batch_status), and
462/// [`add_batch_status_net`](crate::Client::add_batch_status_net) endpoints.
463#[allow(clippy::too_long_first_doc_paragraph)]
464#[derive(Clone, Debug, Serialize)]
465pub struct AddStatusCommon {
466	/// The date for this status.
467	///
468	/// Must be within the past **fourteen days**, or the past **ninety days** for
469	/// accounts with [donation status](https://pvoutput.org/help/donations.html).
470	#[serde(rename = "d", serialize_with = "jiff_ymd_direct")]
471	pub date: Date,
472
473	/// The time for this status.
474	///
475	/// Times will be rounded to the nearest configured
476	/// [status interval](https://pvoutput.org/help/live_data.html#live-configuration-status-interval).
477	#[serde(rename = "t", serialize_with = "jiff_hhmm_direct")]
478	pub time: Time,
479
480	/// Energy generation in watt-hours.
481	///
482	/// This field is **required** when using the batch status endpoint.
483	#[serde(rename = "v1")]
484	pub energy_generation: Option<u32>,
485
486	/// Power generation in watts.
487	#[serde(rename = "v2")]
488	pub power_generation: Option<i32>,
489
490	/// Energy consumption in watt-hours.
491	///
492	/// Limited to **200,000 Wh** for free accounts, or **999,999,999 Wh** for
493	/// accounts with [donation status](https://pvoutput.org/help/donations.html).
494	#[serde(rename = "v3")]
495	pub energy_consumption: Option<u32>,
496
497	/// Power consumption in watts.
498	///
499	/// Limited to **100,000 W** for free accounts, or **2,000,000 W** for
500	/// accounts with [donation status](https://pvoutput.org/help/donations.html).
501	#[serde(rename = "v4")]
502	pub power_consumption: Option<i32>,
503
504	/// Temperature in degrees Celsius.
505	#[serde(rename = "v5")]
506	pub temperature: Option<f32>,
507
508	/// Voltage in volts.
509	#[serde(rename = "v6")]
510	pub voltage: Option<f32>,
511}
512
513impl AddStatusCommon {
514	/// Creates a new [`AddStatusCommon`] for the given date and time with all other fields set to `None`.
515	#[must_use]
516	pub const fn new(date: Date, time: Time) -> Self {
517		Self {
518			date,
519			time,
520			energy_generation: None,
521			power_generation: None,
522			energy_consumption: None,
523			power_consumption: None,
524			temperature: None,
525			voltage: None,
526		}
527	}
528}
529
530/// Extended parameters for use with the [`AddStatusParams`] and [`AddBatchStatusParams`] structs.
531#[derive(Clone, Debug, Serialize, Default)]
532pub struct AddExtendedParams {
533	/// User-defined [extended data](https://pvoutput.org/help/extended_data.html) parameter 1.
534	#[serde(rename = "v7")]
535	pub extended_value_1: Option<f32>,
536
537	/// User-defined [extended data](https://pvoutput.org/help/extended_data.html) parameter 2.
538	#[serde(rename = "v8")]
539	pub extended_value_2: Option<f32>,
540
541	/// User-defined [extended data](https://pvoutput.org/help/extended_data.html) parameter 3.
542	#[serde(rename = "v9")]
543	pub extended_value_3: Option<f32>,
544
545	/// User-defined [extended data](https://pvoutput.org/help/extended_data.html) parameter 4.
546	#[serde(rename = "v10")]
547	pub extended_value_4: Option<f32>,
548
549	/// User-defined [extended data](https://pvoutput.org/help/extended_data.html) parameter 5.
550	#[serde(rename = "v11")]
551	pub extended_value_5: Option<f32>,
552
553	/// User-defined [extended data](https://pvoutput.org/help/extended_data.html) parameter 6.
554	#[serde(rename = "v12")]
555	pub extended_value_6: Option<f32>,
556}
557
558/// Parameters for the [`add_status`](crate::Client::add_status) endpoint.
559#[derive(Clone, Debug, Serialize)]
560pub struct AddStatusParams {
561	/// Parameters common to the [`add_status`](crate::Client::add_status) and
562	/// [`add_batch_status`](crate::Client::add_batch_status) endpoints.
563	#[serde(flatten)]
564	pub common: AddStatusCommon,
565
566	/// Whether to process this status as a cumulative status, and if so, which fields are cumulative.
567	#[serde(rename = "c1")]
568	pub cumulative_flag: CumulativeFlag,
569
570	/// Whether to process this status as net data.
571	///
572	/// See the [PVOutput docs](https://pvoutput.org/help/api_specification.html#net-data) for further information.
573	#[serde(rename = "n", serialize_with = "bool_int")]
574	pub net_data: bool,
575
576	/// Extended data.
577	///
578	/// Requires [donation status](https://pvoutput.org/help/donations.html).
579	#[serde(flatten)]
580	pub extended_data: Option<AddExtendedParams>,
581
582	/// A comment.
583	///
584	/// Limited to a maximum of thirty characters.
585	///
586	/// Requires [donation status](https://pvoutput.org/help/donations.html).
587	#[serde(rename = "m1")]
588	pub message: Option<String>,
589
590	/// Battery information.
591	///
592	/// Requires [donation status](https://pvoutput.org/help/donations.html).
593	#[serde(flatten)]
594	pub battery: Option<AddBatteryParams>,
595}
596
597impl AddStatusParams {
598	/// Creates a new instance of [`AddStatusParams`] with the given common parameters and all other fields set to
599	/// `None`.
600	#[must_use]
601	pub const fn new(common: AddStatusCommon) -> Self {
602		Self {
603			common,
604			cumulative_flag: CumulativeFlag::False,
605			net_data: false,
606			extended_data: None,
607			message: None,
608			battery: None,
609		}
610	}
611}
612
613impl From<AddStatusCommon> for AddStatusParams {
614	fn from(common: AddStatusCommon) -> Self { Self::new(common) }
615}
616
617#[derive(Clone, Debug, Serialize, Default)]
618pub struct AddBatteryParams {
619	/// Battery power in watts.
620	/// A positive value indicates charging either from generation or load (consumption).
621	/// A negative value indicates discharging.
622	#[serde(rename = "b1")]
623	pub power: i32,
624
625	/// State of charge as a percentage.
626	#[serde(rename = "b2")]
627	pub state_of_charge: Option<f32>,
628
629	/// Usable battery size in watt-hours.
630	#[serde(rename = "b3")]
631	pub size: Option<u32>,
632
633	/// Lifetime charge in watt-hours.
634	#[serde(rename = "b4")]
635	pub lifetime_charge: Option<u32>,
636
637	/// Lifetime discharge in watt-hours.
638	#[serde(rename = "b5")]
639	pub lifetime_discharge: Option<u32>,
640
641	/// Battery state.
642	#[serde(rename = "b6")]
643	pub state: Option<BatteryState>,
644}
645
646/// Battery state.
647/// For use with [`AddStatusParams`].
648///
649/// [PVOutput docs](https://pvoutput.org/help/api_specification.html#state-codes)
650// note to future me: deriving repr(int) starts at zero and automatically increments.
651// see https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants
652#[derive(Copy, Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize_repr)]
653#[repr(u8)]
654pub enum BatteryState {
655	Invalid,
656	#[default]
657	Standby,
658	ThermalManagement,
659	Enabled,
660	Fault,
661
662	/// User defined. Corresponds to `b2=5`.
663	UserDefinedA,
664
665	/// User defined. Corresponds to `b2=6`.
666	UserDefinedB,
667
668	/// User defined. Corresponds to `b2=7`.
669	UserDefinedC,
670
671	/// User defined. Corresponds to `b2=8`.
672	UserDefinedD,
673
674	/// User defined. Corresponds to `b2=9`.
675	UserDefinedE,
676}
677
678/// Parameters for the [`add_batch_status`](crate::Client::add_batch_status) endpoint.
679#[derive(Clone, Debug, Serialize)]
680pub struct AddBatchStatusParams {
681	/// Parameters common to the [`add_status`](crate::Client::add_status) and
682	/// [`add_batch_status`](crate::Client::add_batch_status) endpoints.
683	#[serde(flatten)]
684	pub common: AddStatusCommon,
685
686	/// Extended data.
687	///
688	/// Requires [donation status](https://pvoutput.org/help/donations.html).
689	#[serde(flatten)]
690	pub extended_data: Option<AddExtendedParams>,
691}
692
693impl AddBatchStatusParams {
694	/// Creates a new [`AddBatchStatusParams`].
695	#[must_use]
696	pub const fn new(common: AddStatusCommon, extended_data: Option<AddExtendedParams>) -> Self {
697		Self { common, extended_data }
698	}
699}
700
701impl From<AddStatusCommon> for AddBatchStatusParams {
702	fn from(common: AddStatusCommon) -> Self {
703		Self {
704			common,
705			extended_data: None,
706		}
707	}
708}
709
710/// Parameters for the [`add_batch_status_net`](crate::Client::add_batch_status_net) endpoint.
711#[derive(Clone, Debug, Serialize)]
712pub struct AddNetBatchStatusParams {
713	/// The date for this status.
714	#[serde(serialize_with = "jiff_ymd_direct")]
715	pub date: Date,
716
717	/// The time for this status.
718	///
719	/// Times will be rounded to the nearest configured
720	/// [status interval](https://pvoutput.org/help/live_data.html#live-configuration-status-interval).
721	#[serde(serialize_with = "jiff_hhmm_direct")]
722	pub time: Time,
723
724	spacer_a: (),
725
726	/// Power exported in watts.
727	pub power_exported: u32,
728
729	spacer_b: (),
730
731	/// Power imported in watts.
732	pub power_imported: u32,
733}
734
735impl AddNetBatchStatusParams {
736	/// Creates a new [`AddNetBatchStatusParams`] with the provided fields.
737	#[must_use]
738	pub const fn new(date: Date, time: Time, power_exported: u32, power_imported: u32) -> Self {
739		Self {
740			date,
741			time,
742			spacer_a: (),
743			power_exported,
744			spacer_b: (),
745			power_imported,
746		}
747	}
748}
749
750/// Whether to process a status as cumulative, and if so, which of its fields are cumulative.
751#[derive(Clone, Debug, Copy, Default, Serialize, Hash, Eq, PartialEq)]
752pub enum CumulativeFlag {
753	/// This status is not cumulative.
754	#[serde(rename = "0")]
755	#[default]
756	False,
757
758	/// The energy generation and consumption values for this status are lifetime cumulative values.
759	#[serde(rename = "1")]
760	Both,
761
762	/// The energy generation value for this status is lifetime cumulative.
763	#[serde(rename = "2")]
764	Generation,
765
766	/// The energy consumption value for this status is lifetime cumulative.
767	#[serde(rename = "3")]
768	Consumption,
769}
770
771/// Parameters for the [`post_system`](crate::Client::post_system) endpoint.
772///
773/// Updating extended data variables requires [donation status](https://pvoutput.org/help/donations.html).
774#[derive(Clone, Debug, Default)]
775pub struct PostSystemParams {
776	/// A new name for the system.
777	/// Limited to thirty characters.
778	pub name: Option<String>,
779
780	/// Extended data variables.
781	pub(crate) extended_variables: HashMap<ExtendedVariable, ExtendedVariableUpdate>,
782}
783
784impl PostSystemParams {
785	/// Creates a new [`PostSystemParams`].
786	#[must_use]
787	pub fn new() -> Self { Self::default() }
788
789	/// Creates a new [`PostSystemParams`] with the provided `name` and no extended data variables.
790	#[must_use]
791	pub fn new_rename(name: String) -> Self {
792		Self {
793			name: Some(name),
794			extended_variables: HashMap::new(),
795		}
796	}
797
798	/// Adds a new extended variable update.
799	///
800	/// If there is already an update for this variable, it will be replaced with the provided update.
801	///
802	/// # Example
803	/// ```rust
804	/// use pvoutput_client::types::params::{ExtendedVariable, ExtendedVariableUpdate, PostSystemParams};
805	///
806	/// let mut post = PostSystemParams::default();
807	/// let update = ExtendedVariableUpdate {
808	///   label: Some(String::from("Wind speed")),
809	///   unit: Some(String::from("m/s")),
810	///   ..Default::default()
811	/// };
812	/// post.add_update(ExtendedVariable::V7, update);
813	///
814	/// assert_eq!(
815	///   post.get_update(ExtendedVariable::V7).map(|u| u.label.as_deref()).flatten(),
816	///   Some("Wind speed")
817	/// );
818	/// assert!(post.get_update(ExtendedVariable::V8).is_none());
819	/// ```
820	pub fn add_update(&mut self, variable: ExtendedVariable, update: ExtendedVariableUpdate) -> &mut Self {
821		self.extended_variables.insert(variable, update);
822		self
823	}
824
825	/// Gets the update for the given extended variable, if any.
826	#[must_use]
827	pub fn get_update(&self, variable: ExtendedVariable) -> Option<&ExtendedVariableUpdate> {
828		self.extended_variables.get(&variable)
829	}
830}
831
832/// An update to an [extended data variable](https://pvoutput.org/help/extended_data.html).
833///
834/// Any omitted fields will use the previously set values if this extended variable already exists, or the default
835/// values if it doesn't.
836///
837/// # Default colours
838/// | Variable | Colour |
839/// |:--------:|--------|
840/// |    v7    | 339933 |
841/// |    v8    | ccff66 |
842/// |    v9    | efcf21 |
843/// |    v10   | ff5555 |
844/// |    v11   | 0a72b1 |
845/// |    v12   | dd5fc9 |
846#[derive(Debug, Clone, Default)]
847pub struct ExtendedVariableUpdate {
848	/// A label for this extended variable.
849	/// Limited to twenty characters.
850	///
851	/// This field is *required* if this is a new extended variable.
852	pub label: Option<String>,
853
854	/// A unit for this extended variable.
855	/// Limited to ten characters.
856	///
857	/// This field is *required* if this is a new extended variable.
858	pub unit: Option<String>,
859
860	/// A colour to use for this extended variable, formatted as six lowercase hexadecimal digits without leading
861	/// `#` -- for example, `ff0000` for bright red.
862	pub colour: Option<String>,
863
864	/// The axis to use for this extended variable.
865	/// Must be between zero and five.
866	/// The default is zero.
867	///
868	/// Parameters placed on the same axis are shown on the same graph scale.
869	pub axis: Option<u8>,
870
871	/// How to graph this extended variable.
872	/// The default is [`Graph::Line`].
873	pub graph: Option<Graph>,
874}
875
876/// An [extended data variable](https://pvoutput.org/help/extended_data.html) identifier.
877#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
878pub enum ExtendedVariable {
879	/// The first extended variable (`v7`).
880	V7 = 7,
881
882	/// The second extended variable (`v8`).
883	V8 = 8,
884
885	/// The third extended variable (`v9`).
886	V9 = 9,
887
888	/// The fourth extended variable (`v10`).
889	V10 = 10,
890
891	/// The fifth extended variable (`v11`).
892	V11 = 11,
893
894	/// The sixth extended variable (`v12`).
895	V12 = 12,
896}
897
898impl TryFrom<u8> for ExtendedVariable {
899	type Error = u8;
900
901	fn try_from(value: u8) -> Result<Self, Self::Error> {
902		match value {
903			7 => Ok(Self::V7),
904			8 => Ok(Self::V8),
905			9 => Ok(Self::V9),
906			10 => Ok(Self::V10),
907			11 => Ok(Self::V11),
908			12 => Ok(Self::V12),
909			_ => Err(value),
910		}
911	}
912}
913
914/// How to graph an extended variable.
915#[derive(Copy, Clone, Debug, Default, Serialize, Hash, PartialEq, Eq)]
916#[serde(rename_all = "lowercase")]
917pub enum Graph {
918	/// Use an area graph.
919	Area,
920
921	/// Use a line graph.
922	#[default]
923	Line,
924}
925
926impl Display for Graph {
927	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
928		match self {
929			Self::Area => write!(f, "area"),
930			Self::Line => write!(f, "line"),
931		}
932	}
933}
934
935#[derive(Serialize)]
936pub(crate) struct DeleteStatusParams {
937	#[serde(serialize_with = "jiff_ymd_direct")]
938	pub d: Date,
939
940	#[serde(serialize_with = "jiff_hhmm")]
941	pub t: Option<Time>,
942}
943
944// custom serialiser functions
945
946/// Serialises an [`Option<Date>`] to a string in the format "yyyymmdd".
947#[allow(clippy::ref_option)]
948fn jiff_ymd<S>(value: &Option<Date>, s: S) -> Result<S::Ok, S::Error>
949where
950	S: Serializer,
951{
952	if let Some(value) = value {
953		s.serialize_str(value.strftime("%Y%m%d").to_string().as_str())
954	} else {
955		s.serialize_none()
956	}
957}
958
959/// Serialises a [`Date`] to a string in the format "yyyymmdd".
960#[allow(clippy::ref_option)]
961fn jiff_ymd_direct<S>(value: &Date, s: S) -> Result<S::Ok, S::Error>
962where
963	S: Serializer,
964{
965	jiff_ymd(&Some(*value), s)
966}
967
968/// Serialises an [`Option<Time>`] to a string in the format "hh:mm".
969#[allow(clippy::ref_option)]
970fn jiff_hhmm<S>(value: &Option<Time>, s: S) -> Result<S::Ok, S::Error>
971where
972	S: Serializer,
973{
974	if let Some(value) = value {
975		s.serialize_str(value.strftime("%H:%M").to_string().as_str())
976	} else {
977		s.serialize_none()
978	}
979}
980
981/// Serialises a [`Time`] to a string in the format "hh:mm".
982#[allow(clippy::ref_option)]
983fn jiff_hhmm_direct<S>(value: &Time, s: S) -> Result<S::Ok, S::Error>
984where
985	S: Serializer,
986{
987	jiff_hhmm(&Some(*value), s)
988}
989
990/// Serialises a [`bool`] to a string "1" or "0".
991#[allow(clippy::trivially_copy_pass_by_ref)]
992fn bool_int<S>(value: &bool, s: S) -> Result<S::Ok, S::Error>
993where
994	S: Serializer,
995{
996	s.serialize_u8(u8::from(*value))
997}
998
999/// Serialises a latitude and longitude pair to a comma-separated string.
1000#[allow(clippy::ref_option)]
1001fn lat_long<S>(value: &Option<(f32, f32)>, s: S) -> Result<S::Ok, S::Error>
1002where
1003	S: Serializer,
1004{
1005	if let Some(value) = value {
1006		s.serialize_str(format!("{},{}", value.0, value.1).as_str())
1007	} else {
1008		s.serialize_none()
1009	}
1010}