1use chrono::Utc;
4use rand::rngs::StdRng;
5use rand::{Rng, SeedableRng};
6use tokio::sync::mpsc;
7use tokio::time;
8
9use crate::event::{Event, HVACStatus, HumidityReading, TemperatureReading};
10
11#[derive(Debug, Clone)]
13pub struct SimulatorConfig {
14 pub zones: Vec<ZoneConfig>,
15 pub hvac_units: Vec<HVACConfig>,
16 pub events_per_second: u32,
17 pub anomaly_probability: f64,
18 pub degradation_enabled: bool,
19}
20
21#[derive(Debug, Clone)]
22pub struct ZoneConfig {
23 pub id: String,
24 pub name: String,
25 pub target_temp: f64,
26 pub target_humidity: f64,
27 pub temp_variance: f64,
28 pub humidity_variance: f64,
29}
30
31#[derive(Debug, Clone)]
32pub struct HVACConfig {
33 pub id: String,
34 pub base_power: f64,
35 pub base_pressure: f64,
36}
37
38impl Default for SimulatorConfig {
39 fn default() -> Self {
40 Self {
41 zones: vec![
42 ZoneConfig {
43 id: "zone_a".to_string(),
44 name: "Bureaux".to_string(),
45 target_temp: 22.0,
46 target_humidity: 50.0,
47 temp_variance: 1.0,
48 humidity_variance: 5.0,
49 },
50 ZoneConfig {
51 id: "zone_b".to_string(),
52 name: "Salle Serveurs".to_string(),
53 target_temp: 19.0,
54 target_humidity: 50.0,
55 temp_variance: 0.5,
56 humidity_variance: 3.0,
57 },
58 ZoneConfig {
59 id: "zone_c".to_string(),
60 name: "Accueil".to_string(),
61 target_temp: 21.0,
62 target_humidity: 50.0,
63 temp_variance: 2.0,
64 humidity_variance: 8.0,
65 },
66 ],
67 hvac_units: vec![HVACConfig {
68 id: "cta_main".to_string(),
69 base_power: 15.0,
70 base_pressure: 8.5,
71 }],
72 events_per_second: 10,
73 anomaly_probability: 0.01,
74 degradation_enabled: false,
75 }
76 }
77}
78
79#[derive(Debug)]
81pub struct Simulator {
82 config: SimulatorConfig,
83 sender: mpsc::Sender<Event>,
84 tick_count: u64,
85 degradation_factor: f64,
86 rng: StdRng,
87}
88
89impl Simulator {
90 pub fn new(config: SimulatorConfig, sender: mpsc::Sender<Event>) -> Self {
91 Self {
92 config,
93 sender,
94 tick_count: 0,
95 degradation_factor: 1.0,
96 rng: StdRng::from_os_rng(),
97 }
98 }
99
100 pub async fn run(&mut self) {
102 let interval_ms = 1000 / self.config.events_per_second as u64;
103 let mut interval = time::interval(time::Duration::from_millis(interval_ms));
104
105 loop {
106 interval.tick().await;
107 self.tick_count += 1;
108
109 if let Err(e) = self.generate_events().await {
111 tracing::error!("Failed to send event: {}", e);
112 break;
113 }
114
115 if self.config.degradation_enabled {
117 self.degradation_factor += 0.0001;
118 }
119 }
120 }
121
122 async fn generate_events(&mut self) -> Result<(), mpsc::error::SendError<Event>> {
123 let rng = &mut self.rng;
124 let now = Utc::now();
125
126 for zone in &self.config.zones {
128 let is_anomaly = rng.random::<f64>() < self.config.anomaly_probability;
129
130 let temp = if is_anomaly {
131 zone.target_temp + rng.random_range(5.0..10.0)
133 } else {
134 zone.target_temp + rng.random_range(-zone.temp_variance..zone.temp_variance)
135 };
136
137 let reading = TemperatureReading {
138 sensor_id: format!("{}_temp_01", zone.id),
139 zone: zone.id.clone(),
140 value: temp,
141 timestamp: now,
142 };
143 self.sender.send(reading.into()).await?;
144
145 if self.tick_count.is_multiple_of(3) {
147 let humidity = zone.target_humidity
148 + rng.random_range(-zone.humidity_variance..zone.humidity_variance);
149
150 let reading = HumidityReading {
151 sensor_id: format!("{}_hum_01", zone.id),
152 zone: zone.id.clone(),
153 value: humidity,
154 timestamp: now,
155 };
156 self.sender.send(reading.into()).await?;
157 }
158 }
159
160 if self.tick_count.is_multiple_of(5) {
162 for hvac in &self.config.hvac_units {
163 let power = hvac
164 .base_power
165 .mul_add(self.degradation_factor, rng.random_range(-0.5..0.5));
166 let pressure =
167 hvac.base_pressure / self.degradation_factor + rng.random_range(-0.1..0.1);
168
169 let status = HVACStatus {
170 unit_id: hvac.id.clone(),
171 mode: "cooling".to_string(),
172 power_consumption: power,
173 fan_speed: 1200 + rng.random_range(-50..50),
174 compressor_pressure: pressure,
175 timestamp: now,
176 };
177 self.sender.send(status.into()).await?;
178 }
179 }
180
181 Ok(())
182 }
183}
184
185pub fn create_default_simulator() -> (Simulator, mpsc::Receiver<Event>) {
187 let (tx, rx) = mpsc::channel(1000);
188 let config = SimulatorConfig::default();
189 let simulator = Simulator::new(config, tx);
190 (simulator, rx)
191}
192
193pub fn create_anomaly_simulator() -> (Simulator, mpsc::Receiver<Event>) {
195 let (tx, rx) = mpsc::channel(1000);
196 let config = SimulatorConfig {
197 anomaly_probability: 0.1, ..Default::default()
199 };
200 let simulator = Simulator::new(config, tx);
201 (simulator, rx)
202}
203
204pub fn create_degradation_simulator() -> (Simulator, mpsc::Receiver<Event>) {
206 let (tx, rx) = mpsc::channel(1000);
207 let config = SimulatorConfig {
208 degradation_enabled: true,
209 ..Default::default()
210 };
211 let simulator = Simulator::new(config, tx);
212 (simulator, rx)
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
224 fn test_simulator_config_default() {
225 let config = SimulatorConfig::default();
226 assert_eq!(config.zones.len(), 3);
227 assert_eq!(config.hvac_units.len(), 1);
228 assert_eq!(config.events_per_second, 10);
229 assert!((config.anomaly_probability - 0.01).abs() < 0.001);
230 assert!(!config.degradation_enabled);
231 }
232
233 #[test]
234 fn test_zone_config() {
235 let config = SimulatorConfig::default();
236 let zone_a = &config.zones[0];
237 assert_eq!(zone_a.id, "zone_a");
238 assert_eq!(zone_a.name, "Bureaux");
239 assert!((zone_a.target_temp - 22.0).abs() < 0.01);
240 }
241
242 #[test]
243 fn test_hvac_config() {
244 let config = SimulatorConfig::default();
245 let hvac = &config.hvac_units[0];
246 assert_eq!(hvac.id, "cta_main");
247 assert!((hvac.base_power - 15.0).abs() < 0.01);
248 assert!((hvac.base_pressure - 8.5).abs() < 0.01);
249 }
250
251 #[test]
256 fn test_create_default_simulator() {
257 let (sim, _rx) = create_default_simulator();
258 assert_eq!(sim.tick_count, 0);
259 assert!((sim.degradation_factor - 1.0).abs() < 0.001);
260 }
261
262 #[test]
263 fn test_create_anomaly_simulator() {
264 let (sim, _rx) = create_anomaly_simulator();
265 assert!((sim.config.anomaly_probability - 0.1).abs() < 0.001);
266 }
267
268 #[test]
269 fn test_create_degradation_simulator() {
270 let (sim, _rx) = create_degradation_simulator();
271 assert!(sim.config.degradation_enabled);
272 }
273
274 #[test]
275 fn test_simulator_new() {
276 let (tx, _rx) = mpsc::channel(100);
277 let config = SimulatorConfig::default();
278 let sim = Simulator::new(config, tx);
279 assert_eq!(sim.tick_count, 0);
280 }
281
282 #[tokio::test]
287 async fn test_simulator_generate_events() {
288 let (tx, mut rx) = mpsc::channel(100);
289 let config = SimulatorConfig::default();
290 let mut sim = Simulator::new(config, tx);
291
292 sim.generate_events().await.unwrap();
294
295 let mut temp_count = 0;
297 while let Ok(event) = rx.try_recv() {
298 if &*event.event_type == "TemperatureReading" {
299 temp_count += 1;
300 }
301 }
302 assert_eq!(temp_count, 3); }
304
305 #[tokio::test]
306 async fn test_simulator_humidity_generation() {
307 let (tx, mut rx) = mpsc::channel(100);
308 let config = SimulatorConfig::default();
309 let mut sim = Simulator::new(config, tx);
310
311 sim.tick_count = 2; sim.generate_events().await.unwrap();
314 sim.tick_count = 3;
315 sim.generate_events().await.unwrap();
316
317 let mut humidity_count = 0;
318 while let Ok(event) = rx.try_recv() {
319 if &*event.event_type == "HumidityReading" {
320 humidity_count += 1;
321 }
322 }
323 assert!(humidity_count >= 3); }
325
326 #[tokio::test]
327 async fn test_simulator_hvac_generation() {
328 let (tx, mut rx) = mpsc::channel(100);
329 let config = SimulatorConfig::default();
330 let mut sim = Simulator::new(config, tx);
331
332 sim.tick_count = 4;
334 sim.generate_events().await.unwrap();
335 sim.tick_count = 5;
336 sim.generate_events().await.unwrap();
337
338 let mut hvac_count = 0;
339 while let Ok(event) = rx.try_recv() {
340 if &*event.event_type == "HVACStatus" {
341 hvac_count += 1;
342 }
343 }
344 assert!(hvac_count >= 1); }
346
347 #[tokio::test]
348 #[allow(clippy::field_reassign_with_default)]
349 async fn test_simulator_with_degradation() {
350 let (tx, _rx) = mpsc::channel(100);
351 let mut config = SimulatorConfig::default();
352 config.degradation_enabled = true;
353 let mut sim = Simulator::new(config, tx);
354
355 let initial_degradation = sim.degradation_factor;
356
357 for _ in 0..10 {
359 sim.generate_events().await.unwrap();
360 if sim.config.degradation_enabled {
361 sim.degradation_factor += 0.0001;
362 }
363 }
364
365 assert!(sim.degradation_factor > initial_degradation);
367 }
368
369 #[tokio::test]
370 async fn test_simulator_event_fields() {
371 let (tx, mut rx) = mpsc::channel(100);
372 let config = SimulatorConfig::default();
373 let mut sim = Simulator::new(config, tx);
374
375 sim.generate_events().await.unwrap();
376
377 if let Ok(event) = rx.try_recv() {
379 if &*event.event_type == "TemperatureReading" {
380 assert!(event.get_str("sensor_id").is_some());
381 assert!(event.get_str("zone").is_some());
382 assert!(event.get_float("value").is_some());
383 }
384 }
385 }
386
387 #[tokio::test]
388 async fn test_simulator_channel_closed() {
389 let (tx, rx) = mpsc::channel(1);
390 let config = SimulatorConfig::default();
391 let mut sim = Simulator::new(config, tx);
392
393 drop(rx);
395
396 let result = sim.generate_events().await;
398 assert!(result.is_err());
399 }
400}