Skip to main content

bo4e_core/bo/
controllable_resource.rs

1//! Controllable resource (SteuerbareRessource) business object.
2//!
3//! Represents a resource that can be remotely controlled for demand response.
4
5use serde::{Deserialize, Serialize};
6
7use crate::com::Address;
8use crate::enums::{ControllableResourceType, Division, EnergyDirection};
9use crate::traits::{Bo4eMeta, Bo4eObject};
10
11/// A controllable resource for demand response.
12///
13/// German: SteuerbareRessource
14///
15/// Controllable resources can be remotely controlled to
16/// participate in demand response programs.
17///
18/// # Example
19///
20/// ```rust
21/// use bo4e_core::bo::ControllableResource;
22/// use bo4e_core::enums::{Division, ControllableResourceType};
23///
24/// let resource = ControllableResource {
25///     controllable_resource_id: Some("SR001".to_string()),
26///     division: Some(Division::Electricity),
27///     resource_type: Some(ControllableResourceType::OnOff),
28///     ..Default::default()
29/// };
30/// ```
31#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
32#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
33#[cfg_attr(feature = "json-schema", schemars(rename = "SteuerbareRessource"))]
34#[serde(rename_all = "camelCase")]
35pub struct ControllableResource {
36    /// BO4E metadata
37    #[serde(flatten)]
38    pub meta: Bo4eMeta,
39
40    /// Controllable resource ID (SteuerbareRessource-ID)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    #[cfg_attr(feature = "json-schema", schemars(rename = "steuerbareRessourceId"))]
43    pub controllable_resource_id: Option<String>,
44
45    /// Energy division (Sparte)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
48    pub division: Option<Division>,
49
50    /// Resource type (Ressourcentyp)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    #[cfg_attr(feature = "json-schema", schemars(rename = "ressourcentyp"))]
53    pub resource_type: Option<ControllableResourceType>,
54
55    /// Energy direction (Energierichtung)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    #[cfg_attr(feature = "json-schema", schemars(rename = "energierichtung"))]
58    pub energy_direction: Option<EnergyDirection>,
59
60    /// Location address (Standort)
61    #[serde(skip_serializing_if = "Option::is_none")]
62    #[cfg_attr(feature = "json-schema", schemars(rename = "standort"))]
63    pub address: Option<Address>,
64
65    /// Description (Beschreibung)
66    #[serde(skip_serializing_if = "Option::is_none")]
67    #[cfg_attr(feature = "json-schema", schemars(rename = "beschreibung"))]
68    pub description: Option<String>,
69
70    /// Controllable power in kW (Steuerbare Leistung)
71    #[serde(skip_serializing_if = "Option::is_none")]
72    #[cfg_attr(feature = "json-schema", schemars(rename = "steuerbareLeistung"))]
73    pub controllable_power: Option<f64>,
74
75    /// Minimum activation time in minutes (Mindestaktivierungszeit)
76    #[serde(skip_serializing_if = "Option::is_none")]
77    #[cfg_attr(feature = "json-schema", schemars(rename = "mindestaktivierungszeit"))]
78    pub min_activation_time: Option<i32>,
79
80    /// Maximum activation time in minutes (Maximalaktivierungszeit)
81    #[serde(skip_serializing_if = "Option::is_none")]
82    #[cfg_attr(feature = "json-schema", schemars(rename = "maximalaktivierungszeit"))]
83    pub max_activation_time: Option<i32>,
84
85    /// Ramp up time in seconds (Hochlaufzeit)
86    #[serde(skip_serializing_if = "Option::is_none")]
87    #[cfg_attr(feature = "json-schema", schemars(rename = "hochlaufzeit"))]
88    pub ramp_up_time: Option<i32>,
89
90    /// Ramp down time in seconds (Herunterlaufzeit)
91    #[serde(skip_serializing_if = "Option::is_none")]
92    #[cfg_attr(feature = "json-schema", schemars(rename = "herunterlaufzeit"))]
93    pub ramp_down_time: Option<i32>,
94
95    /// Associated technical resource ID
96    #[serde(skip_serializing_if = "Option::is_none")]
97    #[cfg_attr(feature = "json-schema", schemars(rename = "technischeRessourceId"))]
98    pub technical_resource_id: Option<String>,
99
100    /// Associated market location ID
101    #[serde(skip_serializing_if = "Option::is_none")]
102    #[cfg_attr(feature = "json-schema", schemars(rename = "marktlokationsId"))]
103    pub market_location_id: Option<String>,
104
105    /// Is currently active/available (Ist aktiv)
106    #[serde(skip_serializing_if = "Option::is_none")]
107    #[cfg_attr(feature = "json-schema", schemars(rename = "istAktiv"))]
108    pub is_active: Option<bool>,
109}
110
111impl Bo4eObject for ControllableResource {
112    fn type_name_german() -> &'static str {
113        "SteuerbareRessource"
114    }
115
116    fn type_name_english() -> &'static str {
117        "ControllableResource"
118    }
119
120    fn meta(&self) -> &Bo4eMeta {
121        &self.meta
122    }
123
124    fn meta_mut(&mut self) -> &mut Bo4eMeta {
125        &mut self.meta
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_controllable_resource_creation() {
135        let resource = ControllableResource {
136            controllable_resource_id: Some("SR001".to_string()),
137            division: Some(Division::Electricity),
138            resource_type: Some(ControllableResourceType::OnOff),
139            ..Default::default()
140        };
141
142        assert_eq!(resource.controllable_resource_id, Some("SR001".to_string()));
143    }
144
145    #[test]
146    fn test_serialize() {
147        let resource = ControllableResource {
148            meta: Bo4eMeta::with_type("SteuerbareRessource"),
149            controllable_resource_id: Some("SR001".to_string()),
150            ..Default::default()
151        };
152
153        let json = serde_json::to_string(&resource).unwrap();
154        assert!(json.contains(r#""_typ":"SteuerbareRessource""#));
155    }
156
157    #[test]
158    fn test_roundtrip() {
159        let resource = ControllableResource {
160            meta: Bo4eMeta::with_type("SteuerbareRessource"),
161            controllable_resource_id: Some("SR001".to_string()),
162            division: Some(Division::Electricity),
163            resource_type: Some(ControllableResourceType::OnOff),
164            controllable_power: Some(50.0),
165            min_activation_time: Some(15),
166            is_active: Some(true),
167            ..Default::default()
168        };
169
170        let json = serde_json::to_string(&resource).unwrap();
171        let parsed: ControllableResource = serde_json::from_str(&json).unwrap();
172        assert_eq!(resource, parsed);
173    }
174
175    #[test]
176    fn test_bo4e_object_impl() {
177        assert_eq!(
178            ControllableResource::type_name_german(),
179            "SteuerbareRessource"
180        );
181        assert_eq!(
182            ControllableResource::type_name_english(),
183            "ControllableResource"
184        );
185    }
186}