koprogo_api/application/ports/mqtt_energy_port.rs
1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use crate::domain::entities::iot_reading::{DeviceType, MetricType};
7
8/// DTO pour les messages entrants depuis MQTT (Home Assistant → Mosquitto → KoproGo).
9/// Correspond au JSON publié par Home Assistant automations.
10///
11/// Exemple de payload Home Assistant:
12/// ```json
13/// {
14/// "value": 12.47,
15/// "unit": "kWh",
16/// "ts": "2026-03-17T14:00:00Z",
17/// "device_type": "electricity_meter",
18/// "metric_type": "electricity_consumption",
19/// "quality": "good"
20/// }
21/// ```
22#[derive(Debug, Clone, Deserialize, Serialize)]
23pub struct MqttIncomingReadingDto {
24 /// Valeur mesurée
25 pub value: f64,
26 /// Unité (ex: "kWh", "°C", "%")
27 pub unit: String,
28 /// Timestamp ISO8601 émis par le capteur
29 pub ts: DateTime<Utc>,
30 /// Type d'appareil (serde snake_case)
31 pub device_type: DeviceType,
32 /// Type de métrique (serde snake_case)
33 pub metric_type: MetricType,
34 /// Qualité de la mesure ("good", "estimated", "bad")
35 pub quality: Option<String>,
36}
37
38/// Erreurs spécifiques MQTT
39#[derive(Debug, thiserror::Error)]
40pub enum MqttError {
41 #[error("Connection failed: {0}")]
42 ConnectionFailed(String),
43
44 #[error("Subscription failed on topic '{topic}': {reason}")]
45 SubscriptionFailed { topic: String, reason: String },
46
47 #[error("Payload parse error on topic '{topic}': {reason}")]
48 PayloadParseError { topic: String, reason: String },
49
50 #[error("Topic format invalid: {0}")]
51 InvalidTopic(String),
52
53 #[error("Already running")]
54 AlreadyRunning,
55
56 #[error("Not connected")]
57 NotConnected,
58}
59
60/// Port (trait) définissant le contrat pour la couche MQTT.
61/// Le domaine ne connaît pas rumqttc — seul ce trait existe dans l'application layer.
62/// L'adapter `MqttEnergyAdapter` dans infrastructure/mqtt/ implémente ce trait.
63///
64/// Flux: capteur → Home Assistant → Mosquitto (TLS:8883) → MqttEnergyAdapter
65/// → on_reading_received() → RegisterEnergyReadingFromIoTUseCase → DB
66///
67/// Topic pattern: koprogo/{copropriete_id}/energy/{unit_id}/{metric}
68#[async_trait]
69pub trait MqttEnergyPort: Send + Sync {
70 /// Démarre l'écoute MQTT (connect + subscribe).
71 /// Non-bloquant : lance un tokio::spawn en interne.
72 async fn start_listening(&self) -> Result<(), MqttError>;
73
74 /// Arrête proprement l'écoute MQTT.
75 async fn stop_listening(&self) -> Result<(), MqttError>;
76
77 /// Publie un message sortant (ex: alerte anomalie → Home Assistant).
78 /// Topic: koprogo/{copropriete_id}/alerts/{alert_type}
79 async fn publish_alert(
80 &self,
81 copropriete_id: Uuid,
82 alert_type: &str,
83 payload: &str,
84 ) -> Result<(), MqttError>;
85
86 /// Vérifie si le listener est actif.
87 async fn is_running(&self) -> bool;
88}